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 of strings | No | GETS accessor keys, section wildcards such as header.*, or meta.getsValidation.* for the stored validation report. Standard document metadata is also returned. See Select columns and paginate. |
sort | object | No | One exact GETS accessor key and asc or desc order. Wildcards are not supported. |
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
Filter on meta.statuses.document.current to select current document statuses. The example below matches either NEEDS_CORRECTION or NON_COMPLIANT. To inspect stored GETS validation findings, add "columns": ["meta.getsValidation.*"]; API-key query responses do not include status-history arrays.
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
Use GETS accessor keys such as header.documentNumber in columns, without the getsDocument. prefix. The selected GETS fields appear under getsDocument for sales and purchases, and at the document root for receipts. Sort fields also use exact accessor keys such as header.issueDate.
With a nonempty columns array, the response includes the selected fields plus documentId, documentType, meta.source, and current status metadata, when present. Purchase queries also include attribution, when present. Selecting a validation report still returns this standard metadata.
Omitting columns or sending [] returns stored documents without field selection, subject to the API-key status exclusions. Other stored fields, including legacy top-level fields, may therefore appear. Use a nonempty columns array to restrict the document payload. Fields absent from a stored document are not generated by the query.
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"
}
}'Select sections with wildcards
Use a trailing .* to select all registered GETS fields beneath a section, including nested objects. Expansion uses the requested country's GETS schema. It does not select arbitrary database fields.
| Selector | Fields selected |
|---|---|
header.* | Header fields, including document type, exchange rate, and invoice period fields. |
parties.* | All registered party fields. |
parties.seller.* | Seller fields, including nested addresses and identifiers; buyer fields are not selected. |
header.invoicePeriod.* | Invoice-period fields only. |
lineItems.* or lineItems[].* | Registered fields within the line-items array. All stored array entries remain in the result. |
totals.* | Registered totals fields. |
meta.* | Registered GETS metadata fields; see GETS metadata and validation metadata. |
The same syntax works for other registered sections, such as payment.*. You can mix section wildcards and exact fields in one request:
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",
"columns": [
"meta.*",
"header.*",
"parties.*",
"lineItems.*",
"totals.totalAmountIncludingTax"
],
"filters": [
{
"id": "getsDocument.header.issueDate",
"range": {
"gte": "2026-08-01",
"lte": "2026-08-31"
}
},
{
"id": "meta.statuses.document.current",
"value": ["NON_COMPLIANT"]
}
],
"limit": 20,
"page": 1
}'Wildcard selection changes the returned fields, not which documents match. Existing filters, searches, sorting, and pagination still apply. For receipts, the column selectors stay the same, but GETS filter and search paths omit getsDocument..
Use .* at the end of a section path. Bare *, partial names such as partie.*, embedded wildcards such as parties.*.name, and unknown sections are not supported. Wildcards cannot be used in filters, searches, or sort.
Select the GETS validation report
Use "columns": ["meta.getsValidation.*"] to retrieve the complete stored GETS validation report. "columns": ["meta.getsValidation"] is equivalent. This works for sales, purchases, and receipts.
The following example returns one Malaysian sales document matching the date and current-status filters, with its validation report and standard document metadata:
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",
"columns": ["meta.getsValidation.*"],
"filters": [
{
"id": "getsDocument.header.issueDate",
"range": {
"gte": "2026-08-01",
"lte": "2026-08-31"
}
},
{
"id": "meta.statuses.document.current",
"value": ["NON_COMPLIANT"]
}
],
"limit": 1,
"page": 1
}'Illustrative response for a document with a stored validation report:
{
"status": "success",
"data": {
"documents": [
{
"documentId": "01JQEXAMPLE8YQVK1T8DD98VY6N",
"documentType": {
"base": "tax_invoice",
"modifiers": []
},
"meta": {
"getsValidation": {
"success": true,
"errors": [],
"warnings": [],
"metadata": {
"country": "MY",
"base": "tax_invoice",
"modifiers": [],
"variant": null,
"durationMs": 210.01520399999572,
"totals": {
"ruleSets": 1,
"rules": 41,
"passedRules": 39,
"failedRules": 0,
"warningRules": 0,
"skippedRules": 2,
"errors": 0,
"warnings": 0,
"preflights": 3,
"failedPreflights": 0,
"warningPreflights": 0
},
"ruleSetStats": [
{
"name": "my:tax_invoice",
"durationMs": 7.763639999961015,
"ruleCount": 41,
"passedRuleCount": 39,
"failedRuleCount": 0,
"warningRuleCount": 0,
"skippedRuleCount": 2,
"errorCount": 0,
"warningCount": 0
}
]
},
"validatedAt": "2026-08-19T19:00:37.036Z"
},
"statuses": {
"document": { "current": "NON_COMPLIANT" },
"delivery": { "current": "NOT_STARTED" },
"business": { "current": "NOT_STARTED" }
}
}
}
]
},
"pagination": {
"page": 1,
"perPage": 1,
"total": 97,
"totalPages": 97
}
}The report can contain success, errors, warnings, metadata, and validatedAt. Its contents reflect the stored validation run; the query does not run validation again. GETS validation success and the current document status are separate results, as the example shows. A document without a stored report will not have meta.getsValidation in the response.
This selector does not include the GETS document body or meta.xmlValidation. To also retrieve the invoice number, use:
{
"columns": ["header.documentNumber", "meta.getsValidation.*"]
}GETS metadata and validation metadata
| Selector | Response location and meaning |
|---|---|
meta.* | Registered GETS metadata under getsDocument.meta for sales and purchases, or meta for receipts. It does not select the stored GETS validation report. |
meta.getsValidation or meta.getsValidation.* | The complete report at top-level meta.getsValidation, for every document type. |
The validation-report selector is explicitly supported outside the GETS schema. It does not enable arbitrary top-level metadata selectors. Individual report subfields such as meta.getsValidation.errors, report-subsection wildcards, and meta.xmlValidation.* are not currently supported. Select the complete report and read the fields you need from the response.
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.
Status fields in API-key responses
API-key queries retain current public statuses under meta.statuses.document.current, meta.statuses.delivery.current, and meta.statuses.business.current, when present. The legacy meta.statuses.userfacing field can also be returned when stored.
The following fields are excluded from API-key query responses, including requests without columns:
statusHistorymeta.statuses.internalmeta.statuses.document.historymeta.statuses.delivery.historymeta.statuses.business.history
These response exclusions do not delete stored history or change the sales page's status timeline. Signed-in portal queries retain their existing status details. Response shaping is determined by authentication; there is no public request flag to enable internal status details.
Sample successful response
The API returns up to the requested limit of matching documents. This illustrative API-key response uses "columns": ["header.documentNumber"] and shows documents with different current statuses. Source metadata and meta.statuses.userfacing may also appear when stored.
{
"status": "success",
"data": {
"documents": [
{
"documentId": "01JQEXAMPLE8YQVK1T8DD98VY6N",
"documentType": {
"base": "tax_invoice",
"modifiers": []
},
"getsDocument": {
"header": {
"documentNumber": "MY-INV-1001"
}
},
"meta": {
"statuses": {
"document": {
"current": "NEEDS_CORRECTION"
},
"delivery": {
"current": "NOT_STARTED"
},
"business": {
"current": "NOT_STARTED"
}
}
}
},
{
"documentId": "01JQEXAMPLE8YQVK1T8DD98VY6P",
"documentType": {
"base": "tax_invoice",
"modifiers": []
},
"getsDocument": {
"header": {
"documentNumber": "MY-INV-1002"
}
},
"meta": {
"statuses": {
"document": {
"current": "COMPLIANT"
},
"delivery": {
"current": "NOT_STARTED"
},
"business": {
"current": "NOT_STARTED"
}
}
}
},
{
"documentId": "01JQEXAMPLE8YQVK1T8DD98VY6Q",
"documentType": {
"base": "tax_invoice",
"modifiers": []
},
"getsDocument": {
"header": {
"documentNumber": "MY-INV-1003"
}
},
"meta": {
"statuses": {
"document": {
"current": "READY_TO_PROCESS"
},
"delivery": {
"current": "NOT_STARTED"
},
"business": {
"current": "NOT_STARTED"
}
}
}
}
]
},
"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, search, or column selector, invalid request shape, or an invalid sort field or order. |
401 | Missing or invalid authentication. |
403 | The request is not authorized for the workspace. |
500 | Unexpected service or storage error. |
An unknown wildcard section produces an error such as:
{
"status": "error",
"message": "Invalid GETS wildcard column: meata.*"
}Use the exact section name meta.*. A wildcard in sort.field, such as header.*, is also invalid; sort by an exact field such as header.issueDate. Field availability is country-specific, so a section must contain registered fields for the requested country.