List & filtering¶
Every list endpoint in Invora follows one shape, so once you learn it you can list documents, parties, subscriptions, connected businesses, or anything else the same way.
- Path:
POST /api/{area}/{version}/list(e.g./api/v2/documents/list,/api/v1/simple/invoices/list) - Request:
filter,sort,pagination, andmask - Response:
items,totalCount, and an optionalnextPageCursor
curl -X POST https://gateway.invora.app/api/v2/documents/list \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"filter": { "textSearch": "Acme", "part": { "editState": { "inValues": ["EDIT_STATE_FROZEN"] } } },
"sort": { "rules": [ { "createdAt": "SORT_DIRECTION_DESCENDING" } ] },
"pagination": { "limit": "20" },
"mask": "key,editState,documentType"
}'
{
"items": [
{ "key": "doc_01J7...", "editState": "EDIT_STATE_FROZEN", "documentType": "DOCUMENT_TYPE_INVOICE" }
],
"totalCount": "1",
"nextPageCursor": null
}
Filtering¶
filter has three parts, all optional:
| Field | Purpose |
|---|---|
part |
A structured, typed condition tree (see below). |
textSearch |
Fuzzy search across key, party names, notes, and other text fields. |
timeReference |
Query historical state as of a point in time (see Time reference). |
Composable parts¶
part is a single condition. Each service defines its own leaf conditions (typed filters on specific fields) plus three combinators that nest other parts: and, or, and not.
Get all frozen invoices created after 2026-01-01 and in SAR:
{
"filter": {
"part": {
"and": {
"operands": [
{ "createdAt": { "fromInclusive": "2026-01-01T00:00:00Z" } },
{ "editState": { "inValues": ["EDIT_STATE_FROZEN"] } },
{ "field": { "fieldPath": "documentCurrencyCode", "op": "FIELD_FILTER_OPERATOR_EQUAL", "value": { "stringValue": "SAR" } } }
]
}
}
}
}
Leaf conditions are typed per service — e.g. the Simple invoice surface offers key, issueAt, dueDate, branchId, buyerPartyKey, currencyCode, onlyFrozen, onlyDrafts; the Documents API offers key, createdAt, updatedAt, frozenAt, documentType, editState, branchId, and arbitrary UBL field filters via field / unaryField.
Arbitrary field filters (Documents API)¶
For services that expose them, field and unaryField filter on any field path:
{ "field": { "fieldPath": "accountingCustomerParty.party.partyTaxScheme.companyId", "op": "FIELD_FILTER_OPERATOR_EQUAL", "value": { "stringValue": "123456879" } } }
op is a FieldFilterOperator — EQUAL, LESS_THAN(_OR_EQUAL), GREATER_THAN(_OR_EQUAL), IN, CONTAINS, STARTS_WITH, ENDS_WITH, REGEX, ARRAY_CONTAINS, ARRAY_CONTAINS_ALL (each prefixed FIELD_FILTER_OPERATOR_). unaryField takes IS_NULL, IS_SET, or IS_EMPTY.
Sorting¶
sort.rules is an ordered list — earlier rules take priority. Each rule is one field set to SORT_DIRECTION_ASCENDING or SORT_DIRECTION_DESCENDING:
{ "sort": { "rules": [ { "frozenAt": "SORT_DIRECTION_DESCENDING" }, { "createdAt": "SORT_DIRECTION_ASCENDING" } ] } }
The sortable fields are defined per service (e.g. createdAt, updatedAt, issueDate, dueDate, payableAmount, frozenAt, taxAmount).
Pagination¶
One pagination object supports both offset and cursor modes; limit, skip, and cursor are strings:
| Field | Mode | Notes |
|---|---|---|
limit |
both | Max items per page. |
skip |
offset | Skip N items from the start. Page n of size x: skip = (n - 1) * x. |
cursor |
cursor | Pass the previous response's nextPageCursor. |
skip and cursor are mutually exclusive — use limit + skip for page-by-page UIs, limit + cursor for forward iteration over large or live data. When nextPageCursor is absent or empty, there are no more pages.
Field selection (mask)¶
mask is a single comma-separated string of camelCase field paths — restrict a response to just the fields you need:
See Field masks for the full rules (read and write masks).
Time reference¶
timeReference reads entities as they were at a point in time rather than their current state — useful for audit, reconciliation, and reproducing a past report. Its two modes are alternatives — set one or the other, not both:
referenceDate— returns each matching entity as it existed at that instant.historicalData— whentrue, returns all versions over time; group byid.keyin your application to reconstruct each entity's timeline.
This pairs with per-entity versioning — see Entity versioning for reading a specific historical version of a single entity.
Response¶
{
"items": [ /* entities, projected by `mask` */ ],
"totalCount": "123",
"nextPageCursor": "eyJ..."
}
totalCount is the full match count (ignoring pagination). Real-time variants (ListStream) push change events for a filtered list instead of a single page.
Related¶
- gRPC & JSON transcoding — enum, decimal, date, and camelCase conventions used in filters.
- Field masks — the
maskstring for reads and writes. - Entity versioning — point-in-time reads of a single entity.
- Simple invoicing and Documents API — concrete filter parts per surface.
- Error handling — status codes and error details.