Skip to content
campaiDeveloper
campai.com

Many list endpoints accept userFilter to select records by their properties. Receipts, contacts and events use the same filter structure, but each endpoint has its own supported fields. The name means a filter supplied by you; it can query more than user records.

Send userFilter as a string containing JSON, even when the request body is already JSON. Build the filter as an object, then pass JSON.stringify(filter) as the parameter value.

Read Understand money, dates, and data types before mapping amounts, dates, IDs, or missing fields into your own system.

What you’ll have at the end

A JSON-encoded filter built from verified property paths and checked against known records.

  1. Inspect record
  2. Build filter
  3. Encode as string
  4. Check matches
Before you start
  • Read access to the selected endpoint
  • A known matching and nonmatching record
  • Its current schema and enum values
On this page

Start with the Getting started guide for your API key and organization ID. Use a mandate ID and record ID that your key can access.

Open the matching GET endpoint in the API reference and expand its response schema. This shows property names, nested objects, value types and enum values. Then retrieve a known record to see the values your integration will query.

For receipts, use Get receipt:

Example

curl --fail-with-body --silent --show-error --max-time 30 --globoff \
  --request GET 'https://cloud.campai.com/api/YOUR_ORGANIZATION_ID/YOUR_MANDATE_ID/finance/receipts/YOUR_RECEIPT_ID' \
  --header 'X-API-Key: YOUR_API_KEY'

Replace YOUR_API_KEY, YOUR_ORGANIZATION_ID, and YOUR_MANDATE_ID directly in your chosen example. Each tab is complete and uses only cURL or the runtime’s built-in functions. Also replace YOUR_RECEIPT_ID with a receipt your key can access. If you do not have an ID yet, use List receipts with limit: 1 and copy a returned receipt's _id.

A receipt may contain fields like these; this excerpt omits the other response properties:

Example

{
  "type": "invoice",
  "receiptNumber": "INV-2026-1042",
  "receiptDate": "2026-10-18T00:00:00.000Z",
  "paymentStatus": "unpaid",
  "tags": ["annual-membership"],
  "positions": [
    { "description": "Annual membership", "quantity": 2 }
  ]
}

Use the API values, such as invoice and unpaid, in comparisons. Display labels and translated text are not field names or enum values. A nested field uses dot notation: positions.description, for example. Paths are relative to one record, so use paymentStatus, not receipts.paymentStatus or receipts[0].paymentStatus.

The GET response helps you understand the record, but it is not a list of every filterable field. The server checks paths and comparators against the endpoint's property definitions. Computed response fields, such as the receipt's optional summary, are not automatically available to filters. Check the list endpoint's request schema and try one supported condition before combining several.

This filter selects unpaid invoices:

Example

{
  "operator": "and",
  "groups": [
    {
      "operator": "and",
      "fields": [
        { "path": "type", "comparator": "equals", "value": "invoice" },
        { "path": "paymentStatus", "comparator": "equals", "value": "unpaid" }
      ]
    }
  ]
}

The outer operator combines the groups. Each group's operator combines its fields. Use and when every condition must match, or or when any condition may match. The structure has two levels: groups contain fields, without further nested groups.

Each field needs a path and comparator. Most comparators also need a value. For an unfiltered request, omit userFilter; empty groups add no restrictions.

List receipts uses POST, although it reads data. Its userFilter belongs in the JSON body. These equivalent requests read the first page of unpaid invoices:

Example

curl --fail-with-body --silent --show-error --max-time 30 --globoff \
  --request POST 'https://cloud.campai.com/api/YOUR_ORGANIZATION_ID/YOUR_MANDATE_ID/finance/receipts/list' \
  --header 'X-API-Key: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "userFilter": "{\"operator\":\"and\",\"groups\":[{\"operator\":\"and\",\"fields\":[{\"path\":\"type\",\"comparator\":\"equals\",\"value\":\"invoice\"},{\"path\":\"paymentStatus\",\"comparator\":\"equals\",\"value\":\"unpaid\"}]}]}",
  "limit": 25,
  "offset": 0,
  "returnCount": true,
  "sort": {
    "receiptDate": "desc"
  }
}'

The filter below is already encoded as a JSON string. If you construct it from an object in your application, use JSON.stringify(filter) in Node.js or json_encode($filter, JSON_THROW_ON_ERROR) in PHP, then encode the enclosing request body. Passing the filter object directly does not meet the API contract.

Receipt lists default to 50 records and allow up to 100 per page. Keep the same filter and sort when requesting the next page, and increase offset by the number of records received. Stop when a page is empty or you have reached the returned count. Pagination is not a snapshot; changing records can affect later pages.

userFilter combines with other request parameters, including view and searchTerm, and with your organization, mandate and access restrictions. Archived receipts are excluded by default; use the appropriate archived view if you need them. A filter cannot widen the key's access.

Use a comparator supported by the property's type. These are common choices; an endpoint may restrict them further.

Field type Comparators Value
Text equals, notEquals, contains, notContains, startsWith, endsWith A string
Number equals, notEquals, greaterThan, greaterEquals, lessThan, lessEquals A JSON number
Date equals, greaterThan, greaterEquals, lessThan, lessEquals An ISO date string; include a time zone for timestamps
Number or date range between Exactly two values, [start, end], including both bounds
Object ID equals, notEquals A valid 24-character hexadecimal ID string
Text or ID array includes, includesAll, notIncludes A value or array of values
Array empty, notEmpty Omit value
Boolean isTrue, isFalse Omit value
Presence exists, notExists Omit value

includes matches any supplied value; includesAll requires all of them. Use array comparators for array properties such as receipt tags. For a boolean condition, send {"path":"draft","comparator":"isFalse"} without "value": false. Presence checks treat null as absent; for text, an empty string is also absent. Do not pass null as a comparison value.

Text comparisons are case-insensitive. String equality and array membership accept * as a wildcard. Add "literal": true to a field when an asterisk should be matched literally; this does not make the comparison case-sensitive.

This receipt filter matches invoices that are unpaid or partially paid and have a receipt date in October 2026. The outer and requires every group; the status group's or allows either status.

Example

{
  "operator": "and",
  "groups": [
    {
      "operator": "and",
      "fields": [
        { "path": "type", "comparator": "equals", "value": "invoice" },
        { "path": "receiptDate", "comparator": "greaterEquals", "value": "2026-10-01T00:00:00.000Z" },
        { "path": "receiptDate", "comparator": "lessThan", "value": "2026-11-01T00:00:00.000Z" }
      ]
    },
    {
      "operator": "or",
      "fields": [
        { "path": "paymentStatus", "comparator": "equals", "value": "unpaid" },
        { "path": "paymentStatus", "comparator": "equals", "value": "partial" }
      ]
    }
  ]
}

The exclusive upper bound includes the whole last day. Date equality compares an exact instant, so use a range to cover a day or month. These boundaries use UTC; choose explicit offsets if your timestamp query needs another time zone.

To require both receipt tags, add this field to an and group:

Example

{ "path": "tags", "comparator": "includesAll", "value": ["annual-membership", "renewal"] }

For an array of objects, put conditions that must match the same array item in one and group. This filter requires a single receipt position whose description contains “Annual” and whose quantity is at least two:

Example

{
  "operator": "and",
  "groups": [
    {
      "operator": "and",
      "fields": [
        { "path": "positions.description", "comparator": "contains", "value": "Annual" },
        { "path": "positions.quantity", "comparator": "greaterEquals", "value": 2 }
      ]
    }
  ]
}

Splitting those conditions into separate groups can match different positions on the same receipt. Use dot paths without an array index.

Inspect each record's GET response and adapt the field paths before reusing a filter:

Record Inspect the model Submit a filter Example condition
Receipt Get receipt List receipts type equals invoice
Contact Get contact List contacts name contains Morgan
Event Get event List events info.title contains Workshop

Check each endpoint's HTTP method and parameter location. The three list endpoints above use POST bodies. For a GET list endpoint that declares userFilter as a query parameter, such as List proofs, pass the JSON string through URLSearchParams or cURL's --data-urlencode. Do not send a GET body or manually concatenate unescaped JSON into a URL. Each endpoint also has its own page-size limit.

  • If the filter has no effect, inspect the outgoing request. userFilter must be a JSON string with the operator, groups and fields structure. Do not send a MongoDB query such as {"paymentStatus":"unpaid"}.
  • If the server rejects a property or comparator, check the path's spelling, the field type and whether that endpoint supports filtering it. A property in a GET response or sort schema is not necessarily filterable.
  • If the request returns no records, retry with one condition and compare its value with a known record from GET. Check view, searchTerm, mandate access and pagination offset as well.
  • If a boolean or presence condition fails validation, remove value entirely. Comparators such as isTrue and exists do not accept a value.

Check your result

Matching record
Appears under the intended filter.
Nonmatching record
Is excluded.
Unexpected empty list
Remove extra view, selection, and search criteria first.
Continue with Find and filter receipts