Complyance Logo

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/query

Authentication

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/json

The workspace is derived from authentication. Documents from another workspace are never returned.

Request body

FieldTypeRequiredDescription
countrystringYesTwo-letter country code, such as MY, AE, or SA.
typestringYessales, purchases, or receipts.
environmentstringYesTarget environment, normally sandbox or production.
pagenumberNoOne-based page number. Defaults to 1.
limitnumberNoNumber of records per page. Defaults to 20.
filtersarrayNoExact-match, multi-value, range, or existence filters.
searchesarrayNoCase-insensitive text searches. Every supplied search must match.
columnsarray of stringsNoGETS 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.
sortobjectNoOne 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.

SelectorFields 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

SelectorResponse 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 typeDate, amount, and currency filter fieldsText-search fields
sales, purchasesgetsDocument.header.issueDate, getsDocument.header.currency, getsDocument.totals.totalAmountIncludingTaxgetsDocument.header.documentNumber, getsDocument.parties.seller.name, getsDocument.parties.buyer.name
receiptsheader.issueDate, header.currency, totals.totalAmountIncludingTaxheader.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:

  • statusHistory
  • meta.statuses.internal
  • meta.statuses.document.history
  • meta.statuses.delivery.history
  • meta.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

StatusMeaning
400Invalid JSON, unsupported filter, search, or column selector, invalid request shape, or an invalid sort field or order.
401Missing or invalid authentication.
403The request is not authorized for the workspace.
500Unexpected 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.