Skip to main content
The search API lets you query your contacts using a flexible condition-based language. You can filter by any combination of personal info, address, tags, financial activity (transactions, donations, memberships), email campaign engagement, sanctions, and custom fields. Two endpoints share the same query format:
  • POST /search — returns a paginated list of matching contacts
  • POST /kpi — returns only the count of matching contacts, with no contact data

How queries are structured

Every search query is a two-level tree. The outer group sets the top-level logic; each inner group contains the individual conditions.
query
└── $all (AND) or $at_least_one (OR)          ← outer group
    ├── $all or $at_least_one                  ← inner group
    │   ├── $condition { attr, ope, value }
    │   └── $condition { attr, ope, value }
    └── $all or $at_least_one                  ← inner group
        └── $condition { attr, ope, value }
Each group is either:
  • $all — ALL conditions inside must match (AND logic)
  • $at_least_one — AT LEAST ONE condition must match (OR logic)
The minimum shape — even for a single condition — always requires two levels of nesting:
{
  "data": {
    "advanced_search": {
      "per_page": 100,
      "query": {
        "$all": [
          {
            "$all": [
              { "$condition": { "attr": "address.city", "ope": "eql", "value": "Paris" } }
            ]
          }
        ]
      }
    }
  }
}
Example: City is Paris AND has a mobile number AND (tagged volunteer OR tagged donor)
{
  "data": {
    "advanced_search": {
      "per_page": 100,
      "query": {
        "$all": [
          {
            "$all": [
              { "$condition": { "attr": "address.city", "ope": "eql", "value": "Paris" } },
              { "$condition": { "attr": "mobile", "ope": "ext" } }
            ]
          },
          {
            "$at_least_one": [
              { "$condition": { "attr": "tags.name", "ope": "eql", "value": "volunteer" } },
              { "$condition": { "attr": "tags.name", "ope": "eql", "value": "donor" } }
            ]
          }
        ]
      }
    }
  }
}

Operators

What you want to sayope value
equalseql
does not equalnot_eql
has a value / is filled inext
has no value / is emptynot_ext
is before / at mostlte
is after / at leastgte
is between two valuesrange — use with from and to
contains the textcontains
does not contain the textnot_contains
starts withstart_with
does not start withnot_start_with
equals (case-sensitive, exact match)eql:strictdata
matches any of a listany_of
matches none of a listnone_of
matches all of a listall_of
Date fields accept relative values: "now", "now-30d", "now-1y", "now+1y". All dates are interpreted as UTC.

Pagination and sorting

ParameterDescription
per_pageContacts per page — required, 1–1000
pagePage number (default: 0)
sort_attrSort by: surname, firstname, birthdate, gender, lastchange, mail, married_name, city
sort_asctrue = ascending order (default: false)
include_interactionstrue = include each contact’s interaction history in the response

Getting a count without results

Use POST /kpi with the same query body to get the number of matching contacts instantly without fetching the full contact list.
{ "data": { "advanced_search": { "query": { "$all": [ ... ] } } } }
Response: { "data": { "total": 1042 }, "status": "success" }

Examples

Contacts whose membership started on or before today and ends on or after today.
{
  "data": {
    "advanced_search": {
      "per_page": 100,
      "query": {
        "$all": [{
          "$all": [
            { "$condition": { "attr": "memberships.start_date", "ope": "lte", "value": "now" } },
            { "$condition": { "attr": "memberships.end_date",   "ope": "gte", "value": "now" } }
          ]
        }]
      }
    }
  }
}
Amount values are always passed as strings in euros.
{
  "data": {
    "advanced_search": {
      "per_page": 100,
      "query": {
        "$all": [{
          "$all": [
            { "$condition": { "attr": "donations.sum_amount", "ope": "gte", "value": "100" } }
          ]
        }]
      }
    }
  }
}
{
  "data": {
    "advanced_search": {
      "per_page": 100,
      "query": {
        "$all": [{
          "$all": [
            { "$condition": { "attr": "emails.campaign_id",  "ope": "eql",     "value": "42" } },
            { "$condition": { "attr": "emails.delivered_at", "ope": "ext" } },
            { "$condition": { "attr": "emails.opened_at",    "ope": "not_ext" } }
          ]
        }]
      }
    }
  }
}
Members in 2024 who also made at least one donation.
{
  "data": {
    "advanced_search": {
      "per_page": 100,
      "query": {
        "$all": [
          {
            "$all": [
              { "$condition": { "attr": "memberships.period", "ope": "eql", "value": "2024" } }
            ]
          },
          {
            "$all": [
              { "$condition": { "attr": "donations.count_amount", "ope": "gte", "value": "1" } }
            ]
          }
        ]
      }
    }
  }
}
In Paris or Lyon, and has a mobile number.
{
  "data": {
    "advanced_search": {
      "per_page": 100,
      "query": {
        "$all": [
          {
            "$at_least_one": [
              { "$condition": { "attr": "address.city", "ope": "eql", "value": "Paris" } },
              { "$condition": { "attr": "address.city", "ope": "eql", "value": "Lyon" } }
            ]
          },
          {
            "$all": [
              { "$condition": { "attr": "mobile", "ope": "ext" } }
            ]
          }
        ]
      }
    }
  }
}

Attribute reference

Contact fields

AttributeMeaningOperators
firstnameFirst nameeql, not_eql, ext, not_ext, start_with, not_start_with
surnameLast nameeql, not_eql, ext, not_ext, start_with, not_start_with
married_nameMarried nameeql, not_eql, ext, not_ext, start_with, not_start_with
genderGender (M, F, O)eql, not_eql, ext, not_ext
birthdateDate of birtheql, ext, not_ext, range, lte, gte
age_catAge category (0–6)eql, not_eql, ext, not_ext
birthcityCity of birtheql, not_eql, ext, not_ext
birthcountryCountry of birtheql, not_eql, ext, not_ext
nationalityNationalityeql, not_eql, ext, not_ext
mailEmail addresseql:strictdata, not_eql:strictdata, ext, not_ext, start_with, not_start_with
phoneLandlineeql, not_eql, ext, not_ext, start_with, not_start_with
mobileMobile numbereql, not_eql, ext, not_ext, contains, start_with, not_start_with
tags.nameTag nameeql, not_eql, any_of, none_of, all_of, ext, not_ext
black_listBlocked from all communicationeql, not_eql
lastchangeLast modification dateeql, ext, not_ext, range, lte, gte
user_idID of last user who edited the contacteql, not_eql, ext, not_ext
user_contact_idID of the linked Qomon userext, not_ext
action_idsIDs of linked actionseql, not_eql

Address fields

AttributeMeaningOperators
address.cityCityeql, not_eql, ext, not_ext, start_with, not_start_with
address.postalcodePostal codeeql, not_eql, ext, not_ext, start_with, not_start_with
address.countryCountryeql, not_eql, ext, not_ext
address.streetStreet nameeql, not_eql, ext, not_ext, start_with, not_start_with
address.housenumberHouse numbereql, not_eql, ext, not_ext
address.buildingBuildingeql, not_eql, ext, not_ext
address.floorFlooreql, not_eql, ext, not_ext
address.doorDooreql, not_eql, ext, not_ext
address.additionAddress complementeql, not_eql, ext, not_ext
address.pollingstationPolling stationeql, not_eql, any_of, none_of, ext, not_ext
address.invalidAddress flagged as invalideql, not_eql, ext, not_ext

Financial fields

These fields match contacts who have at least one matching record. For example, transactions.amount gte "50" returns contacts with at least one transaction ≥ €50, not contacts whose total is ≥ €50 — use sum_amount for totals. Amount fields are in euros and must always be passed as strings: "value": "50" not "value": 50.
Transactions (transactions.*)
AttributeMeaningOperators
transactions.created_atRecord creation datelte, gte, range
transactions.dateTransaction execution dateeql, not_eql, lte, gte, range
transactions.amountAmount of one transaction (€)eql, not_eql, lte, gte, range
transactions.sum_amountTotal amount across all transactions (€)eql, not_eql, lte, gte, range
transactions.count_amountNumber of transactionseql, not_eql, lte, gte, range
transactions.code_campaignCampaign codeext, not_ext, eql, not_eql, contains, not_contains, start_with, not_start_with
transactions.payment_method_kindPayment methodeql, not_eql
transactions.status_idTransaction status IDeql, not_eql
Donations (donations.*)
AttributeMeaningOperators
donations.created_atRecord creation dateeql, not_eql, lte, gte, range
donations.transaction_dateDonation operation dateeql, not_eql, lte, gte, range
donations.amountAmount of one donation (€)eql, not_eql, lte, gte, range
donations.sum_amountTotal donated (€)eql, not_eql, lte, gte, range
donations.count_amountNumber of donationseql, not_eql, lte, gte, range
donations.donation_price_idDonation type/tier IDeql, not_eql
Memberships (memberships.*)
AttributeMeaningOperators
memberships.created_atRecord creation dateeql, not_eql, lte, gte, range
memberships.start_dateMembership start dateeql, not_eql, lte, gte, range
memberships.end_dateMembership end dateeql, not_eql, lte, gte, range
memberships.transaction_dateMembership operation dateeql, not_eql, lte, gte, range
memberships.periodActive year — e.g. "2024" means active that calendar yeareql, not_eql, lte, gte, range
memberships.multiple_in_periodHad more than one membership in a given yeareql (value = year string e.g. "2024")
memberships.membership_price_idMembership type/tier IDeql, not_eql

Email engagement fields

These match contacts who have at least one email record where that field is set. A contact with no email history will not match ext or not_ext.
AttributeMeaningOperators
emails.created_atEmail record dateext, not_ext, lte, gte, range
emails.typeEmail typeext, not_ext, eql, not_eql
emails.senderSender addressext, not_ext, eql, not_eql
emails.subjectEmail subject lineext, not_ext, contains, not_contains, start_with, not_start_with
emails.user_idID of Qomon user who sent the campaignext, not_ext, eql, not_eql
emails.campaign_idCampaign IDext, not_ext, eql, not_eql
emails.campaign_nameCampaign nameext, not_ext, contains, not_contains, start_with, not_start_with
emails.campaign_goalCampaign goalext, not_ext, eql, not_eql
emails.campaign_is_successfulCampaign marked successfulext, not_ext, eql, not_eql
emails.campaign_performance_ratingCampaign performance score (0–100)ext, not_ext, lte, gte, range
emails.template_idTemplate IDext, not_ext, eql, not_eql
emails.template_nameTemplate nameext, not_ext, contains, not_contains, start_with, not_start_with
emails.delivered_atEmail delivered dateext, not_ext, lte, gte, range
emails.opened_atEmail opened dateext, not_ext, lte, gte, range
emails.clicked_atEmail clicked dateext, not_ext, lte, gte, range
emails.bounced_atEmail bounced dateext, not_ext, lte, gte, range
emails.unsubscribed_atUnsubscribed dateext, not_ext, lte, gte, range
emails.blacklisted_atBlacklisted dateext, not_ext, lte, gte, range
emails.abuse_report_atAbuse report dateext, not_ext, lte, gte, range
emails.clicked_urlsA URL that was clickedext, not_ext, eql, not_eql, contains, start_with

Sanctions fields

sanctions.validity is a computed field — it does not store a date, it tells you whether a sanction is currently active: "ongoing" (started and not yet ended) or "ended" (end date is in the past).
AttributeMeaningOperators
sanctions.created_atSanction creation dateeql, not_eql, lte, gte, range
sanctions.start_dateSanction start dateeql, not_eql, lte, gte, range
sanctions.end_dateSanction end dateeql, not_eql, ext, not_ext, lte, gte, range
sanctions.statusSanction statuseql, not_eql, contains, not_contains
sanctions.validity"ongoing" or "ended"eql

Custom fields and form answers

These attributes use a different condition shape — you must also provide form_id and form_ref_ids:
AttributeWhat it searchesOperators
custom_fieldsCustom fields attached to contactsext, not_ext, eql, not_eql, contains, not_contains, lte, gte, range
formSurvey, consent, level of support, presence, or task form answersext, not_ext, eql, not_eql, contains, not_contains, lte, gte, range
{
  "$condition": {
    "attr": "custom_fields",
    "ope": "eql",
    "form_id": 1001,
    "form_ref_ids": [2001]
  }
}
Retrieve your form_id values from GET /v1/forms/type/{type} and form_ref_ids from GET /forms/{id}.
ext / not_ext only check whether the contact has filled in the given form_idform_ref_ids is ignored. eql without a value checks that all listed form_ref_ids are present. eql with a value additionally checks that value matches the stored answer.
Last modified on June 8, 2026