Skip to content

Field masks

A field mask restricts which fields an operation touches. Invora uses one for two jobs:

  1. Read masks — on Get and List, return only the listed fields (smaller payloads).
  2. Write masks — on Update, modify only the listed fields (partial updates).

The mask is a comma-separated string

Shape & casing

Over JSON, mask is a single comma-separated string of field paths in camelCase — the same names you use in the request body. It is not an object and not {"paths": [...]}, and the paths are not snake_case.

// CORRECT
{ "mask": "key,editState,calculations.payableAmount" }

// WRONG
{ "mask": { "paths": ["edit_state", "calculations.payable_amount"] } }

Nested paths use dots (calculations.payableAmount); list multiple paths separated by commas with no spaces required.

Read masks

Pass mask on a Get or List request to receive only specific fields. Unrequested fields come back zero-valued (empty string, 0, null, or omitted).

Get with a mask (skip the heavy UBL content)

curl -X GET "https://gateway.invora.app/api/v2/documents/doc_01J7...?mask=editState,documentType,frozenAt" \
  -H "Authorization: Bearer $TOKEN"
Response
{
  "details": {
    "editState": "EDIT_STATE_FROZEN",
    "documentType": "DOCUMENT_TYPE_INVOICE",
    "frozenAt": "2026-04-29T10:00:00Z"
  }
}

This skips the full UBL content and xmlContent — ideal when you only need metadata.

List with a mask

curl -X POST https://gateway.invora.app/api/v2/documents/list \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "pagination": { "limit": "20" },
    "mask": "key,documentType,editState,branchId"
  }'

Common read projections

Use case Mask
Dashboard list key,documentType,editState,prefix,sequenceId,frozenAt,branchId
Status check key,editState,regulationMetadata
Full document Omit mask entirely

Write masks

Pass mask on an Update request alongside concurrencyStamp. Only the listed fields are modified; everything else is left as-is.

Update only the customer party on an invoice

curl -X PUT https://gateway.invora.app/api/v2/documents/doc_01J7... \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "concurrencyStamp": "a1b2c3d4",
    "mask": "content.invoice.accountingCustomerParty",
    "changes": {
      "content": {
        "invoice": {
          "accountingCustomerParty": {
            "party": { "partyName": [{ "name": { "value": "Updated Customer Name" } }] }
          }
        }
      }
    }
  }'

Settings partial update

curl -X PUT https://gateway.invora.app/api/v2/settings/self-party \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "mask": "pdf,defaultCurrency",
    "changes": { "pdf": { "showLogo": true, "showQrCode": true }, "defaultCurrency": "SAR" }
  }'

Only pdf and defaultCurrency change; prefixes, regulation config, and the rest stay untouched.

Write-mask rules

  1. Omitting the mask on Update replaces the entire content — use with care.
  2. An empty mask ("") is a no-op — nothing is updated.
  3. Nested paths use dots: content.invoice.legalMonetaryTotal.payableAmount.
  4. Repeated fields are replaced wholesale (not merged) when their parent is in the mask.
  5. Map fields follow the same replace semantics.
  6. Paths are camelCase — the same names as the JSON body, not the proto snake_case.

Streaming with masks

ListStream and GetStream accept a mask too. It applies to every event in the stream and cannot change mid-stream — open a new stream to change the projection.