# Get actions
Source: https://developers.qomon.com/api-reference/actions/get-actions
/src/autogen/incoming-actions-openapi3.0.yaml get /actions
Get actions
# Create a contact
Source: https://developers.qomon.com/api-reference/contacts/create-a-contact
/src/autogen/incoming-openapi3.0.yaml post /contacts
Creates a contact in the Qomon database.
To synchronize and mass update your contacts with Qomon database, use `POST /contacts/upsert` instead. This synchronous endpoint costs 5 rate-limit tokens per request.
# Delete a contact by ID
Source: https://developers.qomon.com/api-reference/contacts/delete-a-contact-by-id
/src/autogen/incoming-openapi3.0.yaml delete /contacts/{contactId}
# Find a contact by ID
Source: https://developers.qomon.com/api-reference/contacts/find-a-contact-by-id
/src/autogen/incoming-openapi3.0.yaml get /contacts/{contactId}
Returns a single contact by ID.
# Update contact by ID
Source: https://developers.qomon.com/api-reference/contacts/update-contact-by-id
/src/autogen/incoming-openapi3.0.yaml patch /contacts/{contactId}
Fully replace an existing contact in the Qomon database (behaves like PUT).
This endpoint uses the PATCH verb but behaves like a PUT (full replacement). Any fields that are NOT included in the payload will be cleared/removed. Always send the full contact object if you don't want to lose existing data. To synchronize and mass update your contacts with Qomon database, use `POST /contacts/upsert` instead. This synchronous endpoint costs 5 rate-limit tokens per request.To specify the advanced fields (survey, consents, etc), you need to use the `formdatas` structure with `form_id` and `form_ref_id`.
* `form_id`: the ID of the advanced field
* `form_ref_id`: the ID of the value of the advanced field
* `data`: free text if the field ("forms") type is Text, Integer, or Date (input format: `YYYY-MM-DD'T'HH:mm:ss.SSS'Z'`; responses may return up to microsecond precision: `YYYY-MM-DD'T'HH:mm:ss.SSSSSS'Z'`); otherwise, use the value of the refvalue
* `date`: the date of the formdata entry
```json
"formdatas": [
{
"form_id": 47566,
"form_ref_id": 15648,
"data": "consent_email"
}
]
```
To specify the custom fields, you need to use the `custom_fields` structure with `form_id` and `form_ref_id`.
* `form_id`: the ID of the custom field
* `form_ref_id`: the ID of the value of the custom field
* `data`: free text if the field ("forms") type is Text, Integer, or Date (input format: `YYYY-MM-DD'T'HH:mm:ss.SSS'Z'`; responses may return up to microsecond precision: `YYYY-MM-DD'T'HH:mm:ss.SSSSSS'Z'`); otherwise, use the value of the refvalue
* `date`: the date of the custom\_field entry
```json
"custom_fields": [
{
"form_id": 879854,
"form_ref_id": 98741,
"data": "hamburger",
"date": "2024-07-17T11:34:42.195Z"
}
]
```
NB:
* Each patch will generate new ids for formdatas and custom\_fields. (due to the full replace behavior: soft delete + recreate)
* If you don't send the exhaustive list of formdatas or custom\_fields, the missing ones will be deleted (soft).
* For Text, Integer and Date Fields, if you add a formdata or custom\_field, it will be added to the existing ones.
* For Radio (single select), if you add a formdata or custom\_field, it will replace the existing ones.
* For Checkbox (multi-select) fields, any new value will be added to the existing ones.
* If you change a formdata or custom\_field, it will update it. (the id is not required here because of the full-replace behavior).
# Upsert a contact
Source: https://developers.qomon.com/api-reference/contacts/upsert-a-contact
/src/autogen/incoming-openapi3.0.yaml post /contacts/upsert
Asynchronously creates or updates a contact in the Qomon database. See the upsert rules in the documentation.
# Get a form
Source: https://developers.qomon.com/api-reference/forms/get-a-form
/src/autogen/incoming-openapi3.0.yaml get /forms/{id}
Returns a form by its numeric ID. Passing a type string (e.g. `consent`) is deprecated — use `GET /v1/forms/type/{type}` instead.
# List forms by type
Source: https://developers.qomon.com/api-reference/forms/list-forms-by-type
/src/autogen/incoming-openapi3.0.yaml get /v1/forms/type/{type}
Returns forms for a specific type. Allowed values: consent, survey, level_of_support, custom_fields, presence_status, tasks.
# Delete an invitation
Source: https://developers.qomon.com/api-reference/invitations/delete-an-invitation
/src/autogen/users-openapi3.0.yaml delete /users/invitation/{id}
Delete the invitation with the given invitation id.
Deleting an invitation will prevent the invited user from accepting it and joining the group, but it will not revoke access if the invitation has already been accepted. To revoke access for an already accepted invitation, you will need to remove the user from the group directly.
# Invite users by their emails
Source: https://developers.qomon.com/api-reference/invitations/invite-users-by-their-emails
/src/autogen/users-openapi3.0.yaml post /users/invitation
Send an invitation to the specified mail addresses.
Invitations are sent asynchronously, so the response will only indicate that the invitation request has been received, not that the emails have been sent. To check the status of invitations, use `GET /users/invitation` to retrieve the list of invitations and their details.
# Retrieve all invitations
Source: https://developers.qomon.com/api-reference/invitations/retrieve-all-invitations
/src/autogen/users-openapi3.0.yaml get /users/invitation
Get all invitations in the sender-selected space.
# Send a global message to provided teams
Source: https://developers.qomon.com/api-reference/notifications/send-a-global-message-to-provided-teams
/src/autogen/users-openapi3.0.yaml post /global-message
Sends a global message to the specified teams in the sender-selected space.
# Retrieve role collection
Source: https://developers.qomon.com/api-reference/roles/retrieve-role-collection
/src/autogen/users-openapi3.0.yaml get /roles
Retrieve all roles available in the sender-selected space.
# Contact count KPI
Source: https://developers.qomon.com/api-reference/search/contact-count-kpi
/src/autogen/incoming-search-openapi3.0.yaml post /kpi
Return KPI totals for contacts matching a query.
# Search contacts
Source: https://developers.qomon.com/api-reference/search/search-contacts
/src/autogen/incoming-search-openapi3.0.yaml post /search
Search contacts matching a query.
# Get transaction settings
Source: https://developers.qomon.com/api-reference/settings-and-configuration/get-transaction-settings
/src/autogen/donation-membership-transaction-openapi3.0.yaml get /v1/transaction_settings
Return transaction settings for the authenticated group, including enabled payment method kinds, default status ID, default price IDs, batch and membership serial configuration, and open periods.
Use `payment_method_kinds` to find the allowed values for `payment_method_kind` on transactions. Use `default_status_id` as the `status_id` when creating new valid transactions.
# List code campaigns
Source: https://developers.qomon.com/api-reference/settings-and-configuration/list-code-campaigns
/src/autogen/donation-membership-transaction-openapi3.0.yaml get /v1/code_campaigns
Return all campaign codes configured for the group. Use the returned codes as `code_campaign` on transactions.
# List donation prices
Source: https://developers.qomon.com/api-reference/settings-and-configuration/list-donation-prices
/src/autogen/donation-membership-transaction-openapi3.0.yaml get /v1/donation_prices
Return all donation prices configured for the group. Use the returned IDs as `donation_price_id` when creating donations.
# List membership price groups
Source: https://developers.qomon.com/api-reference/settings-and-configuration/list-membership-price-groups
/src/autogen/donation-membership-transaction-openapi3.0.yaml get /v1/membership_price_groups
Return all membership price groups configured for the group, each containing its associated membership prices.
# List membership prices
Source: https://developers.qomon.com/api-reference/settings-and-configuration/list-membership-prices
/src/autogen/donation-membership-transaction-openapi3.0.yaml get /v1/membership_prices
Return all membership prices configured for the group. Use the returned IDs as `membership_price_id` when creating memberships.
# List transaction statuses
Source: https://developers.qomon.com/api-reference/settings-and-configuration/list-transaction-statuses
/src/autogen/donation-membership-transaction-openapi3.0.yaml get /v1/transaction_statuses
Return all transaction statuses configured for the group. Each status has a kind: valid, unpaid, reimbursed, bank_error, or other.
Use these IDs as `status_id` when creating or updating transactions. For refunds use the status with `kind: reimbursed`; for cancellations use `kind: other`.
# Create a new team
Source: https://developers.qomon.com/api-reference/teams/create-a-new-team
/src/autogen/users-openapi3.0.yaml post /teams
Creates a new team.
# Delete a team
Source: https://developers.qomon.com/api-reference/teams/delete-a-team
/src/autogen/users-openapi3.0.yaml delete /teams/{id}
Soft-deletes the team with the given ID.
# Retrieve a team
Source: https://developers.qomon.com/api-reference/teams/retrieve-a-team
/src/autogen/users-openapi3.0.yaml get /teams/{id}
Returns the team with the given ID.
# Retrieve all teams
Source: https://developers.qomon.com/api-reference/teams/retrieve-all-teams
/src/autogen/users-openapi3.0.yaml get /teams
Returns every team the caller has access to.
# Retrieve public teams
Source: https://developers.qomon.com/api-reference/teams/retrieve-public-teams
/src/autogen/users-openapi3.0.yaml get /teams/public
Returns only the public teams the caller has access to.
# Update an existing team (full replace)
Source: https://developers.qomon.com/api-reference/teams/update-an-existing-team-full-replace
/src/autogen/users-openapi3.0.yaml patch /teams
Updates the team with the given ID.
This endpoint performs a full replace of the team, meaning that any field not included in the payload will be reset to its zero value.
# Create a transaction bundle
Source: https://developers.qomon.com/api-reference/transaction-bundles/create-a-transaction-bundle
/src/autogen/donation-membership-transaction-openapi3.0.yaml post /v1/transaction_bundles
Create a transaction bundle grouping one or more transactions with optional memberships and donations. The bundle is recorded in the space associated with the API key. At least one transaction is required.
**One API key = one space.** The bundle is recorded in the space of the API key you use. The payer contact can belong to a different space.
**Payer vs. beneficiary:** `contact_id` in `transactions` is the payer; `contact_id` in `memberships` or `donations` is the beneficiary. They can differ (Tiers Payant).
**Required before creating:** retrieve `payment_method_kinds` and `default_status_id` from `GET /v1/transaction_settings`; membership price IDs from `GET /v1/membership_prices`; donation price IDs from `GET /v1/donation_prices`.
**`external_transaction_id`** must be a plain integer — string suffixes are not supported.
# Delete a transaction bundle
Source: https://developers.qomon.com/api-reference/transaction-bundles/delete-a-transaction-bundle
/src/autogen/donation-membership-transaction-openapi3.0.yaml delete /v1/transaction_bundles/{id}
Delete a transaction bundle and all its associated transactions, memberships, and donations.
# Get a transaction bundle
Source: https://developers.qomon.com/api-reference/transaction-bundles/get-a-transaction-bundle
/src/autogen/donation-membership-transaction-openapi3.0.yaml get /v1/transaction_bundles/{id}
Return a single transaction bundle by ID, including all associated transactions, memberships, and donations.
# Get transaction bundle history
Source: https://developers.qomon.com/api-reference/transaction-bundles/get-transaction-bundle-history
/src/autogen/donation-membership-transaction-openapi3.0.yaml get /v1/transaction_bundles/{id}/history
Return the complete change history for a transaction bundle. Each entry records a creation, modification, or deletion of a transaction, membership, donation, or status change.
# List transaction bundles
Source: https://developers.qomon.com/api-reference/transaction-bundles/list-transaction-bundles
/src/autogen/donation-membership-transaction-openapi3.0.yaml get /v1/transaction_bundles
Return a paginated list of transaction bundles for the authenticated group, ordered by creation date (newest first). Maximum limit is 1000. A bundle updated via PATCH does not move to the top of the list.
Results are ordered by creation date, newest first. To find a stop point when fetching a date range, stop paginating as soon as you encounter a bundle older than your target date.
# Update a transaction bundle
Source: https://developers.qomon.com/api-reference/transaction-bundles/update-a-transaction-bundle
/src/autogen/donation-membership-transaction-openapi3.0.yaml patch /v1/transaction_bundles/{id}
Update an existing transaction bundle. Updates are additive — items not included in the request remain unchanged. Include the item `id` to update an existing item; omit `id` to add a new item.
**Additive updates:** items not included in the request arrays are left unchanged.
**Updating an existing item:** include its `id` plus all required fields — omitted required fields may be cleared.
**Adding a new item:** omit `id`.
**Partial refund:** PATCH the transaction with the reimbursed status and `reimbursed_amount`, and update the `amount` on the linked membership or donation.
**Full cancellation:** `DELETE /v1/transaction_bundles/{id}` then recreate with `POST /v1/transaction_bundles`.
# Delete a user
Source: https://developers.qomon.com/api-reference/users/delete-a-user
/src/autogen/users-openapi3.0.yaml delete /users/{id}
Removes a user from the selected space.
# Edit user's role
Source: https://developers.qomon.com/api-reference/users/edit-users-role
/src/autogen/users-openapi3.0.yaml patch /users/role
Changes a user's role in the selected space.
# Get a user
Source: https://developers.qomon.com/api-reference/users/get-a-user
/src/autogen/users-openapi3.0.yaml get /users/{id}
Returns a single user by ID.
# List users
Source: https://developers.qomon.com/api-reference/users/list-users
/src/autogen/users-openapi3.0.yaml get /users
Returns users from the selected space.
# Update a user
Source: https://developers.qomon.com/api-reference/users/update-a-user
/src/autogen/users-openapi3.0.yaml patch /users/{id}
Partially updates a user.
# Data synchronization
Source: https://developers.qomon.com/pages/v1/api-polling
Fetch contacts created or updated since your last polling window by querying the Search API on a schedule.
If you are building an integration to keep your external database or CRM synchronized with Qomon, poll the Search API on a schedule to retrieve contacts created or updated within a specific timeframe.
## Endpoint
Use the `advanced_search` object with `POST /search`.
* Endpoint: `https://incoming.qomon.app/search`
* Headers: `Content-Type: application/json` and `Authorization: Bearer YOUR_API_KEY`
```json theme={"system"}
{
"data": {
"advanced_search": {
"page": 0,
"per_page": 1000,
"query": {
"$all": [
{
"$all": [
{
"$condition": {
"attr": "UpdatedAt",
"ope": "gte",
"value": "now-1d/d"
}
}
]
}
]
}
}
}
}
```
## How the date filter works
The polling logic relies on the `$condition` object inside your query.
* `attr: "UpdatedAt"` targets the timestamp when a contact was created or last modified in Qomon.
* `ope: "gte"` means "greater than or equal to". The API returns any record updated during or after the time you pass.
* `value: "now-1d/d"` uses server-side date math. It means "now minus one day, rounded down to the start of the day."
Adjust the relative date to match your polling frequency. For example, use `now-1h` for an hourly sync.
## Handle pagination
The Search API accepts `per_page` values up to `1000`, so your integration should keep requesting pages until it reaches the end of the result set.
1. Start with `"page": 0`.
2. Process the returned contacts.
3. If the response contains `1000` contacts, increment `page` and send the same request again.
4. Stop when the API returns fewer contacts than your `per_page` value.
## Related docs
See **Search > Overview** for the full query structure, supported operators, and additional examples.
# Build vs. Qomon
Source: https://developers.qomon.com/pages/v1/build-vs-qomon
## Should You Build In-House or Use Qomon?
It's a fair question. You have teams and sometimes engineers, or the budget for them. You have specific needs. You'd rather not hand your data to a vendor. So why pay for a system when you could build your own?
Here's a straight answer.
## Why Qomon
Qomon is purpose-built for one thing: activating supporters. Not a generic CRM, not a marketing suite bent to work for you. The whole platform is designed around the supporter and what they do with you, from first contact online or on the field to data management to fundraising, in one unified place.
And, it’s not just standing still. A full team is working on Qomon every day, and it improves constantly. In the variety of things you need to do, we're here to manage activations, events, canvassing and more. Our teams are specialized in these areas to make sure you always have the latest tools.
Because it's used across 70+ countries, you benefit from tactics that have been proven to work globally: when one organization finds a better way to organize, fundraise, or mobilize, it shows up in the tool you already use. You inherit the whole network's learning without lifting a finger.
Qomon is also built to fit you. The platform is modular, so you turn on what you need and leave the rest. With a full API and MCP, you can build directly on top of Qomon, with Certified Experts available to help if you don’t have the skills internally. You get a solid and evolving infrastructure with the freedom to extend it, instead of having to choose between the two.
That's the short version: the most specialized, most evolving, most extendable infrastructure for supporter activation & mobilization, backed by a team that does this and only this.
Now for the build-vs-buy question itself.
## When building makes sense
Build in-house when the software itself is your advantage and your core focus, and when you have a permanent team to own it. If you can fund engineering, maintenance, compliance, and a multi-year roadmap without it competing against everything else you need to do, building can be the right call.
That's a narrow case. Most organizations who say "we'll just build it" don't fit it. They're confusing *can* with *should*.
## What building actually costs
The common mistake is pricing the build and forgetting everything after it.
You're not buying a tool once. You're committing to:
* **Maintenance forever, 24/7.** Things break at the worst time, on the oldest devices, in the field. Someone has to be on call.
* **Compliance that keeps moving.** GDPR, TTPA, state data laws, consent rules, email or sms/mms deliverability standards. They change, and your code has to change with them every time.
* **Managing payments and security systems** often means handing off critical components to external providers because certifications and compliance requirements are difficult to obtain and maintain in-house. As a result, you end up relying on multiple third-party vendors, even if that wasn't your original intention.
* **A roadmap you'll never finish.** You'll ship the first module, then need the next ten: contacts, events, segmentation, fundraising, reporting, integrations, deduplication. Each one is another quarter.
* **Key-person risk.** The one person who understood how it worked just left.
Building is rarely the expensive part. Owning it is.
## How does Qomon help you build on top?
You don't have to choose between buying a predetermined set or building from scratch. Qomon gives you the foundation and the tools to extend it:
* **API-first infrastructure and MCP** so you build on top of Qomon instead of around it, and plug it into the rest of your stack. You can also schedule automatic exports of your contact data straight to your own S3 storage, recurring, filtered by the criteria you choose, no manual pulls required.
* **Certified Expert Program** where vetted partners and specialists help you design, build, and ship what you need on the platform, faster than you would alone.
* **Collaborative Development Initiative** to push your ideas into the product. Learn more [here](https://qomon.com/resources/blog/article/qomons-co-development-model).
* **Strong security standards with a unique dual infrastructure.** You choose your server location and the legal framework that suits your organization. A specialized team handles security and data requests for our sector specifically, so your data is protected by people who understand how sensitive organizing and advocacy actually work.
* **Own your data.** Owning your data and owning the servers are not the same thing; at Qomon, you get full ownership and portability of your data. Plus, run [automatic exports](https://help.qomon.com/en/articles/8968241-automatic-exports-on-qomon).
## The hidden trap.
When you build for the task in front of you, you optimize one tool at a time. Slowly, you end up with a stack of disconnected tools that don't talk to each other - your contacts live as separate rows in each one, and the whole project becomes obsolete or non-compliant - quickly.
What that describes is a traditional filing system. What actually works today is one clear, unified view of each person and what they do with you, with everything connected. Building piece by piece almost always pushes you the wrong way.
## So, build or buy?
That's your call. At Qomon, we build the infrastructure so you can focus on your mission, activating your supporters, growing your movement, and winning. You bring the strategy and the relationships. We carry the platform, the maintenance, the compliance, and the constant improvement, so you don't have to. We are here to make sure Action Wins!
# LLMs.txt
Source: https://developers.qomon.com/pages/v1/build-with-llms
Use Qomon's LLM-ready documentation files to give AI assistants structured context about the API.
These files help AI tools discover available guides, endpoint references, and API specifications before they answer questions or generate code.
## Choose this when:
* You want an AI assistant to read public documentation before it answers.
* You need a quick index of the site or a full documentation export in one file.
* You do not need live access to a specific Qomon space.
## Available files
* [`/llms.txt`](/llms.txt): A concise index of the documentation site, including page links and descriptions.
* [`/llms-full.txt`](/llms-full.txt): A full documentation export for tools that need broader context in one file.
## When to use them
Use these files when you want an AI assistant to:
* Understand the Qomon API documentation structure.
* Find the right API reference pages before suggesting endpoints.
* Generate integration code with better documentation context.
* Answer implementation questions using the latest public docs.
Use MCP instead when the assistant needs live data from your Qomon space.
## Suggested prompt
```text theme={"system"}
Use the Qomon API documentation context from /llms.txt.
If you need full documentation context, use /llms-full.txt.
Help me build an integration that [describe what you want to build].
```
## Best practices
* Start with [`/llms.txt`](/llms.txt) when your tool can fetch linked pages.
* Use [`/llms-full.txt`](/llms-full.txt) when your tool works better with a single context file.
* Keep your API key private. Never paste it into public or untrusted AI tools.
* Verify generated code against the API reference before using it in production.
# Changelog
Source: https://developers.qomon.com/pages/v1/changelog
# Collaborative Development Initiative
Source: https://developers.qomon.com/pages/v1/collaborative_development_initiative
Discover our original Collaborative Development Initiative at Qomon, built for our sector and what makes it special and fully aligned with our mission: delivering the best tools - innovative, scalable, and advanced, at the best possible cost.
## What is the Collaborative Development Initiative and why is it here?
At Qomon, we only work with political and nonprofit organizations. And because our sector is so unique: the agency of volunteers, the complexity of local chapters, the challenge of reaching thousands of people with limited resources, we know that standard software development models don't really work.
Political and nonprofit organizations have something in common: huge needs, incredible complexity, and limited budgets. Most can't independently fund the tools they actually need. That's why we created the Collaborative Development Initiative.
Our[ Collaborative Development ](https://qomon.com/resources/blog/article/qomons-co-development-model)Initiative allows organizations across the sector to finance new features together. By mutualizing innovation, advanced tools become accessible without requiring any single organization to carry the full cost and more importantly the hidden trap of runway costs and stalled evolution.
An organization, or a group of organizations, can request and co-build a new feature that isn’t yet on the product roadmap - costs shared.
## How does it work?
* You come with your idea, challenge or user storiy and share them with the rest of the team.
* Qomon Product & Engineering teams evaluate the request and schedule a chat
* If it aligns with Qomon’s mission, product guidelines, and long-term vision, we estimate the time, resources, and scope required.
* The development cost is shared between the organization and Qomon.
* The client covers part of the cost through their subscription and a reduced development fee.
* Qomon covers the rest (usually between 30% and 80%), reflecting our commitment to bringing the feature into the core Qomon capabilities.
* The feature is delivered to the requesting organization within a defined timeline.
* After a private period of 3 to 12 months, exclusivity ends and the feature becomes available to all Qomon organizations.
## What’s in it for organizations?
* Early access to a strategic feature.
* Lower costs compared to fully custom development and more importantly, runaway costs.
* Direct influence on the product roadmap and on how the feature is designed for real use cases.
* No future burden: Qomon handles maintenance and ongoing improvements as part of the product.
Your supporters, volunteers, and donors deserve an organization that's fully focused on them, not just on keeping infrastructure running. Qomon's Collaborative Development Initiative is here to work with your teams to build the tools they deserve! \
\
*Alternative traditional development exist, just speak to our team.* Your supporters, volunteers, and donors deserve an organization that's fully focused on them, not just on keeping infrastructure running. Qomon's Collaborative Development Initiative is here to work with your teams to build the tools they deserve!
*Alternative traditional development exist, just speak to our team.*
# Welcome
Source: https://developers.qomon.com/pages/v1/discover
Welcome to the Qomon Public API!\
\
Qomon is your API-first platform, purpose-built for political and nonprofit teams and their supporters.\
\
Open up endless possibilities by connecting Qomon to your ecosystem of tools, or build your own system on top of Qomon: create integrations, automate workflows, sync your tools, and move your supporters to action faster.
## **Let's get you started**
The Qomon Public API & MCP is built to help you integrate seamlessly with any third-party application or AI system, whether it is an existing platform or a newly developed custom system.
You can use the API to perform CRUD (create, read, update, delete) operations at no extra cost.
This includes working with resources such as Contacts, Search, Actions, Users, Fundraising and more use cases to come thanks to your feedback.
Set up your API keys and connect Qomon to your ecosystem.
Integrate AI tools or assistants with Qomon or build on top using LLMs & Qomon MCP
## **Let’s build together**
If you need assistance or have any blockers, please contact our engineering team at [engine@qomon.com](mailto:engine@qomon.com)
Qomon is built collaboratively. If your use case isn't covered yet, contact us. We're always looking for ways to improve our API.
## **Description of API Objects**
Create and manage your contacts in Qomon.
Manage transactions, memberships, and donations.
Query your contacts with flexible AND/OR conditions.
Manage users, teams, roles and permissions directly through Qomon.
Retrieve a list and info on your events and fields actions.
## **Useful Links**
Stay up to date with the latest improvements from Qomon.
Create an account or login to an existing one.
Learn about Qomon's approach to our product roadmap.
Real-time availability and status updates for Qomon’s core services.
Master mobilization strategy and the Qomon platform.
Build features in collaboration with Qomon.
Open infrastructure to grow with you.
Access articles and resources directly from the Community Team.
Discover the differences between building in-house or using Qomon.
# Getting started
Source: https://developers.qomon.com/pages/v1/getting-started
## Your API keys
Your API key authenticates requests to the Qomon Public API. Treat it like a password and keep it private.
### Find your API keys
You can also open the [API keys page](https://qomon.app/settings/extensions/connect) directly.
## Choose your API domain
Use `https://incoming.qomon.app` for API requests by default.
Only use `https://incoming-us.qomon.app` when your space's API settings explicitly mention the `incoming-us` subdomain.
When following examples in these docs, replace only the host if your space uses the US domain. Keep the same endpoint path.
## Authentication
Send your API key in the `Authorization` header on every request. You can regenerate or delete keys when you need to rotate access. For security measures, keys can be regenerated or deleted as needed.
### Request headers
Use the bearer token format:
`Authorization: Bearer 7ZOrUe4ZTmy-Rzjn-CjJ1Q`
## Rate limits
Read the [rate limits](/pages/v1/rate-limits) page before you build imports, synchronization jobs, or other write-heavy integrations.
# MCP Server
Source: https://developers.qomon.com/pages/v1/mcp
Connect Claude or ChatGPT to your Qomon space and query live data in real time.
## Overview
The Qomon MCP server lets you connect your Qomon space directly to AI assistants like Claude and ChatGPT.
Unlike documentation files, MCP gives your assistant **live access** to your data — contacts, actions, users, fundraising — without copy-pasting anything.
## Choose this when
* You want an assistant to read live data from a specific Qomon space.
* You need answers based on current records, not just public docs.
* You are already past the point where documentation context alone is enough.
The MCP server authenticates via an API key tied to a specific Qomon space. Each space requires its own key and its own connector.
***
## Create a Qomon account
Before connecting Qomon to your AI assistant, you need a Qomon account and access to a workspace. If you don't have one yet, here's how to get started in 2 minutes.
1. Go to [**qomon.com**](http://qomon.com)
2. Click **Login** — then **Create a new account** if you don't have one yet
3. Once logged in, select or create the **workspace** you want to connect
***
## Prerequisites
Before connecting, generate an API key for the Qomon space you want to query:
1. Go to your Qomon space → **Settings → Integrations & API**
2. Under **Generate API Keys**, click **+ Add**
3. Name your key (e.g. `mcp-claude`) and copy it — you'll need it during setup
***
## Connect to Claude
Available on the **Team plan**. Once added at the organization level, all team members have access automatically — no individual setup required.
Go to [claude.ai](https://claude.ai) → **Settings** → **Connectors**
In the Name field, enter `qomon-mcp`. In the "Remote MCP server URL" field, paste `https://mcp.qomon.ai/mcp` and click **Add**.
Scroll to find **qomon-mcp** and click **Connect**
An OAuth popup opens — paste your **Qomon API key**, select your region, and click **Connect**
Open a new chat — You're all set! Try asking Claude to search your contacts, list actions, or browse fundraising data
Browser cookie issue. Retry in a **private/incognito window**.
Always open a **new conversation** — existing chats won't pick up new connectors.
Each space needs its own connector and API key. Contact your Claude Team admin to add a new one.
***
## Connect to ChatGPT
In **beta**. Requires a **Plus or Pro** subscription with Developer Mode enabled.
Go to [chatgpt.com](https://chatgpt.com) (Web version) → **Settings** → **Apps**
In **Settings → Connectors**, click **Create**
Go to [**chatgpt.com**](http://chatgpt.com) (web version) → **Settings** → **Apps** → **Advanced settings** → toggle **Developer mode** on, then click **Create app**
```text theme={"system"}
https://mcp.qomon.ai/mcp
```
A "New App" form opens. Fill in: **Name** → `Qomon`, **Connection** → paste `https://mcp.qomon.ai/mcp`, **Authentication** → leave OAuth. Check "I understand and want to continue" and click **Create**
A Qomon OAuth popup opens — select your **region**, paste your **Qomon API key**, and click **Connect**
New chat → click **+** → **More** → **Developer Mode** → **Add sources** → enable Qomon
Your Qomon app now appears under **Enabled apps** in Settings → Apps
Open a new chat. You're all set! Try asking ChatGPT to search your contacts, list actions, or browse fundraising data
If you only need public documentation context, use [`/llms.txt`](/llms.txt) or [`/llms-full.txt`](/llms-full.txt) instead.
***
## Connect to your own model
The Qomon MCP server follows the open **Model Context Protocol (MCP)** standard — which means it works with any MCP-compatible AI assistant or environment, not just Claude or ChatGPT. Cursor, Windsurf, Continue, or any custom LLM setup can connect to it the same way.
Use the following MCP server URL:
```text theme={"system"}
https://mcp.qomon.ai/mcp
```
If you want to give your model context about the Qomon API (endpoints, structure, guides) rather than live workspace data, check out the [LLM-Ready Documentation](https://developers.qomon.com/pages/v1/build-with-llms) instead.
***
## Best use cases
Once Qomon is connected to your AI assistant, here are the prompts that work best.
On ChatGPT Plus/Pro, MCP connectors are **read-only**. Write operations require a Business or Enterprise plan.
| Profile / Use case | What you can do | Example prompt |
| :------------------- | :---------------------------------------------------------------- | :---------------------------------------------------------------------------------------- |
| **Field organizer** | Search and segment contacts by tag, location, or support level | *"Find all contacts tagged 'volunteer' in London with an email address"* |
| **Campaign manager** | Analyze your actions — participation, status, history | *"List my last 10 canvassing actions and their participation rates"* |
| **Fundraising team** | Browse donations and transactions in real time | *"Show me all donations above \$500 from the last 30 days"* |
| **Operations** | Query your team members and org structure | *"Who are the active users in my organization?"* |
| **Data entry** | Create or update contacts directly from a conversation | *"Create a new contact: Jane Smith, [jane@example.com](mailto:jane@example.com), London"* |
| **Reporting** | Cross-reference contacts, actions and donations for a quick brief | *"How many contacts attended an action in Birmingham this year?"* |
# Native Integrations
Source: https://developers.qomon.com/pages/v1/native-integrations
# Public Roadmap
Source: https://developers.qomon.com/pages/v1/public-roadmap
## Does Qomon have a public roadmap?
A question we get often: where's your public roadmap? We used to have one.
Today, we don't.
Not because we're hiding our plans, but because of how we like to build, alongside the organizations and movements we serve.
Here's a look internally.
## We set big initiatives every year
Every year we pick a handful of initiatives that move Qomon's products forward: an offline mode for canvassing, smarter workflows to turn contacts into action, deeper insights from your data with AI, a social feed on your action to keep your volunteers motivated. We love talking about where we're heading whenever it's useful, and we share these big bets openly.
But our sector, and the way we build in 2026, runs on reactivity. What's true today won't necessarily be true in three months, technically or otherwise. And the causes we serve face emergencies that can't wait. So we anchor on outcomes, not a calendar. Within the frame of those big initiatives, we'd rather reprioritize and stay flexible.
## We reprioritize every cycle
This is the heart of it. We revisit development every month, because the world you work in moves fast. A moment can appear in days and shape a whole season.
So each month we ask: what matters most right now, what did we learn from users, where can we create the most value for the movement next? Then we adjust.
We adjust based on what users tell us. Every six weeks we run [user days](https://qomon.com/resources/blog/article/users-days-your-requests-your-roadmap), and our [Collaborative Development Initiative](https://qomon.com/resources/blog/article/qomons-co-development-model) can reshape priorities too.
The steady part is our product vision. We know what aligns and what doesn't, and we hold firm on the big goals and initiatives we set for the year.
## We build in cycles: Qomon is a Toyota
Our work runs in focused cycles where the team commits to a clear set of outcomes and ships them. Value arrives steadily, cycle after cycle, instead of piling up for one big release far down the line.
If you like cars, think Toyota. If you buy a Toyota Prius in 2025, the 2026 model won't be identical, because Toyota keeps refining it from what drivers actually report. Other carmakers wait five years for the next version of the model. We'd rather improve continuously, shaped by what you tell us along the way.
**Qomon is a Toyota.**
## What this means
Our roadmap is a list of non-negotiable initiatives for the year, with the flexibility to move as conditions change. For us, a roadmap isn't a promise about dates. It's a shared direction we build together, one cycle at a time.
\
If you'd like to learn more about our product organization or our major initiatives, feel free to get in touch.
# Rate limits
Source: https://developers.qomon.com/pages/v1/rate-limits
## How rate limiting works
The API uses a token bucket.
* Bucket capacity: `100` tokens
* Refill rate: `10` tokens per second
* Default request cost: `1` token
* Expensive request cost: `5` tokens
All requests authenticated against the space share the same bucket.
## Expensive endpoints
These synchronous contact write endpoints cost `5` tokens per request:
* `POST /contacts`
* `PATCH /contacts/{id}`
If you want the lower-cost write path, use `POST /contacts/upsert`. It keeps the default request cost of `1` token and is the recommended route for synchronization and mass updates.
## Response headers
Every rate-limited authenticated response includes these headers:
| Header | Meaning |
| ----------------------- | ------------------------------------------------ |
| `X-Ratelimit-Limit` | Bucket shape in the format `burst:100;rate:10.0` |
| `X-Ratelimit-Remaining` | Tokens currently available in your bucket |
| `X-Ratelimit-Cost` | Cost of the request that was just evaluated |
| `X-Ratelimit-Reset` | Seconds until the bucket is fully refilled |
When the API rejects a request with `429 Too Many Requests`, the response also includes:
| Header | Meaning |
| ------------- | ------------------------------------------------------ |
| `Retry-After` | Whole seconds to wait before retrying the same request |
## Example
```http theme={"system"}
X-Ratelimit-Limit: burst:100;rate:10.0
X-Ratelimit-Remaining: 94
X-Ratelimit-Cost: 5
X-Ratelimit-Reset: 0.6
```
* Prefer `POST /contacts/upsert` for large imports and recurring syncs.
* Spread bursts when you send expensive synchronous contact writes.
* Read `Retry-After` before retrying a `429` response.
# Create and update contacts
Source: https://developers.qomon.com/pages/v1/reference/contacts/contact-form-fields
Choose the right contact write endpoint, understand upsert matching, and structure advanced fields for contact create and update workflows.
## Which endpoint should you use?
For most use cases, use `POST /contacts/upsert`. It handles both creating and updating contacts in a single call, with automatic deduplication based on ID, email, or name/birthdate/address.
For specific scenarios, two synchronous alternatives are available:
* `POST /contacts` — creates a single contact and returns it immediately; useful when you need the new contact's ID right away.
* `PATCH /contacts/{id}` — updates a known contact by ID; useful for targeted edits when you already have the whole contact object and ID.
## Write payload shapes by endpoint
`POST /contacts/upsert` accepts high-level contact payloads. For advanced fields, use field-specific arrays such as `consents`, `status`, `forms`, `custom_fields`, `actions`, or `presence_status`.
Use `POST /contacts` or `PATCH /contacts/{id}` when you need synchronous create or update behavior. For advanced fields, both endpoints use the lower-level `formdatas` structure to link a contact to a form and a selected value.
## Upsert matching strategy
When upserting, the system tries to find an existing contact to update using the following strategy (in order). If no match is found, a new contact is created.
If an `id` is provided and matches an existing contact, that contact is updated. Accepts a Qomon contact ID or a NationBuilder ID.
If no ID is provided (or it didn't match), the system searches for contacts with the same `mail`. Among the results, `firstname` and `surname` are used as additional filters if provided. An empty value means the field is not used for matching.
If no email match was found and a `surname` is provided, the system searches by surname. `firstname` must also match. If both contacts have a `birthdate`, it must match exactly. If both have an `address`, `city` or `postalcode` must match — and if no birthdate comparison was made, `street` must also match.
If none of the above strategies found a match, a new contact is created.
If the `id` or `label` of an advanced field, or the `value` (for non-free-text fields), does not exist, the contact will not be updated or created. **Errors will not be logged.**
## Advanced fields (form fields)
In `POST /contacts/upsert`, several fields in the `contact` object accept **arrays of form objects**. Each object requires a form `id` or unique `label`, and a string `value` defined in your account settings.
Each form type has its own endpoint. Call the relevant one to retrieve the IDs and accepted values for your account:
| Field | Endpoint |
| :-------------- | :------------------------------------------------------ |
| `consents` | `GET https://incoming.qomon.app/forms/consent` |
| `custom_fields` | `GET https://incoming.qomon.app/forms/custom_fields` |
| `status` | `GET https://incoming.qomon.app/forms/level_of_support` |
For more information, look at [the reference for /forms](/api-reference/forms/retrieve-forms-by-their-type).
Each field type has a dedicated endpoint. Call it to get the valid IDs and accepted values for your account:
| Field family | Payload key | Endpoint |
| :--------------- | :---------------- | :------------------------------------ |
| Consents | `consents` | `GET /v1/forms/type/consent` |
| Custom fields | `custom_fields` | `GET /v1/forms/type/custom_fields` |
| Level of support | `status` | `GET /v1/forms/type/level_of_support` |
| Survey answers | `forms` | `GET /v1/forms/type/survey` |
| Tasks | `actions` | `GET /v1/forms/type/tasks` |
| Presence status | `presence_status` | `GET /v1/forms/type/presence_status` |
See the [Forms overview](/pages/v1/reference/forms/overview) for more details about form types and `formdatas`.
Each form field follows the same shape: an array of objects with an id and a string value.
If the form has a **unique** label, you can use the label instead of the id.
```json theme={"system"}
{
"kind": "contact",
"data": {
"firstname": "Jane",
"surname": "Doe",
"gender": "F",
"mail": "jane.doe@example.com",
"address": {
"postalcode": "12345"
},
"consents": [
{ "id": 1234, "value": "consent_email" },
{ "label": "General communication", "value": "consent_sms" }
],
"custom_fields": [
{ "id": 4512, "value": "member" }
],
"status": [
{ "id": 9826, "value": "donator" }
]
}
}
```
Each form field is an array of objects with an `id` and a `value`. If a form has a **unique** label, you can use `label` instead of `id`.
```json theme={"system"}
{
"kind": "contact",
"data": {
"firstname": "Jane",
"surname": "Doe",
"mail": "jane.doe@example.com",
"consents": [
{ "id": 1234, "value": "consent_email" },
{ "label": "General communication", "value": "consent_sms" }
],
"custom_fields": [
{ "id": 4512, "value": "member" }
],
"status": [
{ "id": 9826, "value": "donator" }
],
"forms": [
{ "id": 7621, "value": "Interested in volunteering" }
],
"actions": [
{ "id": 2314, "value": "send_information" }
]
}
}
```
Use the form `id` returned by the endpoint, not the `form_ref_id`.
The `value` field always expects a string — never a boolean or integer.
Every form field must be an array, even when setting a single value.
## Related reference pages
* See [Handle form structure](/pages/v1/reference/forms/overview) to decode `formdatas` values when you fetch contacts.
* See [Contacts overview](/pages/v1/reference/contacts/overview) for the contact object model.
# Overview
Source: https://developers.qomon.com/pages/v1/reference/contacts/overview
## Contact object
A contact represents an individual in your Qomon space. Contact data is split into basic fields and advanced fields.
## Basic fields
Basic fields are simple key-value pairs for core contact information:
```json theme={"system"}
{
"firstname": "John",
"surname": "Doe",
"mail": "john.doe@example.com",
"mobile": "0123456789",
"address": {
"housenumber": "123",
"street": "Rue de la paix",
"city": "Paris",
"country": "France"
},
"tags": ["important", "vip"]
}
```
## Advanced fields
Advanced fields store structured information such as consents, survey answers, custom fields, presence status, level of support, and tasks.
When you retrieve contacts, advanced fields appear in the `formdatas` structure with `form_id` and `form_ref_id`. The same structure is also used by synchronous contact writes. Use `GET /forms/{id}` to decode those IDs.
## Related guides
* See [Create and update contacts](/pages/v1/reference/contacts/contact-form-fields) for endpoint selection, upsert matching, and write payload examples.
* See [Handle form structure](/pages/v1/reference/forms/overview) for form types and `formdatas` decoding.
# Handle form structure
Source: https://developers.qomon.com/pages/v1/reference/forms/overview
Forms define the advanced fields you can attach to contacts, such as consents, surveys, level of support, custom fields, presence status, and tasks.
## Form types
| Type | Description |
| ------------------ | ------------------------------------- |
| `consent` | GDPR or custom consent checkboxes |
| `survey` | Multi-question survey forms |
| `level_of_support` | Political or engagement level ratings |
| `custom_fields` | Arbitrary key-value fields |
| `presence_status` | Attendance or presence tracking |
| `tasks` | Action or task forms |
## Fetching a form
Use `GET /v1/forms/type/{type}` to list all forms of a given type (the canonical route).
`GET /forms/{id}` retrieves a single form by its numeric ID. Passing a type string to `/{arg}` (e.g. `/forms/consent`) is **deprecated** — use `/v1/forms/type/consent` instead.
## Reading advanced fields on a contact
When you fetch a contact, advanced fields appear as `formdatas` entries. This is the lower-level structure used to associate a contact with a form and a selected value:
```json theme={"system"}
{
"formdatas": [
{
"form_id": 12,
"form_ref_id": 45
}
]
}
```
* `form_id` — the ID of the form (advanced field definition)
* `form_ref_id` — the ID of the selected value/option within that form
Use `GET /forms/{id}` to decode the form definition and map `form_ref_id` to a human-readable value.
## Setting advanced fields on a contact
`POST /contacts` and `PATCH /contacts/{id}` can also use `formdatas` to link a contact to a form and a selected value. Use `GET /v1/forms/type/{type}` to discover form definitions and `GET /forms/{id}` to decode the form and option IDs.
For write payload examples and endpoint-specific contact update guidance, see [Create and update contacts](/pages/v1/reference/contacts/contact-form-fields).
# Create transaction bundles
Source: https://developers.qomon.com/pages/v1/reference/fundraising/create-transaction-bundles
How to create a transaction bundle with transactions, memberships, and donations.
## What you need before creating
Retrieve these group-specific values before making your first `POST /v1/transaction_bundles` call:
| What you need | How to get it |
| :----------------------------------- | :------------------------------------------------------ |
| Allowed `payment_method_kind` values | `GET /v1/transaction_settings` → `payment_method_kinds` |
| Default valid `status_id` | `GET /v1/transaction_settings` → `default_status_id` |
| Membership price IDs | `GET /v1/membership_prices` |
| Donation price IDs | `GET /v1/donation_prices` |
| Campaign codes | `GET /v1/code_campaigns` |
See [Reference data](/pages/v1/reference/fundraising/reference-data) for details on each lookup endpoint.
## Minimal request: payment only
The only required array is `transactions`. A bundle with just a transaction records the payment without attaching a membership or donation.
```json theme={"system"}
{
"data": {
"transactions": [
{
"contact_id": 123456,
"amount": 2000,
"currency": "eur",
"payment_method_kind": "CB",
"date": "2026-03-29T12:59:52+02:00",
"status_id": 1
}
]
}
}
```
> Use the `default_status_id` from `GET /v1/transaction_settings` — not a hardcoded `1`.
## With a membership
```json theme={"system"}
{
"data": {
"transactions": [
{
"contact_id": 123456,
"amount": 2000,
"currency": "eur",
"payment_method_kind": "CB",
"date": "2026-03-29T12:59:52+02:00",
"external_transaction_id": 1001,
"status_id": 1,
"comment": "Imported via API"
}
],
"memberships": [
{
"contact_id": 123456,
"membership_price_id": 10,
"start_date": "2026-01-01T00:00:00Z",
"end_date": "2026-12-31T00:00:00Z",
"amount": 2000,
"amount_initial": 2000,
"currency": "eur"
}
]
}
}
```
## With a donation
```json theme={"system"}
{
"data": {
"transactions": [
{
"contact_id": 123456,
"amount": 5000,
"currency": "eur",
"payment_method_kind": "CB",
"date": "2026-03-27T18:00:28+02:00",
"external_transaction_id": 1002,
"status_id": 1
}
],
"donations": [
{
"contact_id": 123456,
"amount": 5000,
"amount_initial": 5000,
"currency": "eur",
"donation_price_id": 20
}
]
}
}
```
## Different payer and beneficiary (third party payer)
`contact_id` in `transactions` is the **payer**. `contact_id` in `memberships` or `donations` is the **beneficiary**. They can be different people.
```json theme={"system"}
{
"data": {
"transactions": [
{
"contact_id": 123456,
"amount": 2000,
"currency": "eur",
"payment_method_kind": "CB",
"date": "2026-03-29T10:00:00Z",
"external_transaction_id": 1003,
"status_id": 1
}
],
"memberships": [
{
"contact_id": 789012,
"membership_price_id": 10,
"start_date": "2026-01-01T00:00:00Z",
"end_date": "2026-12-31T00:00:00Z",
"amount": 2000,
"amount_initial": 2000,
"currency": "eur"
}
]
}
}
```
Here contact `123456` paid, but the membership is attributed to contact `789012`.
## Field notes
**`external_transaction_id`** — use this to link the bundle to your own system's record. Must be a plain integer; strings are not supported.
**Dates** — all date fields must be valid ISO 8601 timestamps. Invalid calendar dates (e.g. `2025-02-31`) are rejected.
**`amount_initial`** — always set this to the original price-list amount. When `amount` differs (discount or partial payment), `amount_initial` preserves the original value for reporting.
# Overview
Source: https://developers.qomon.com/pages/v1/reference/fundraising/overview
Manage transactions, memberships, and donations through the Qomon API.
**Qomon Fundraising is a separate add-on**. [See pricing](https://qomon.com/pricing) or reach out to your account manager to get access.
Financial data in Qomon is organized around **transaction bundles**. A bundle represents a single payment event and groups together:
* **Transactions** — the payment itself (amount, method, payer)
* **Memberships** — membership subscriptions attached to the payment
* **Donations** — donations attached to the payment
One bundle typically contains one transaction, but the memberships and donations within it can be attributed to different contacts than the payer.
For an overview of the transactions feature in the Qomon web app — including provider configuration and dashboard concepts — see the [Qomon help center: Overview of the transactions feature](https://help.qomon.com/en/articles/11369755-overview-the-transactions-feature-on-qomon).
## One API key = one space
The bundle is recorded in the space whose API key you use. The contact can belong to a different space — their profile will still show the transaction. This is the correct pattern for multi-space organizations (e.g. a national federation and local chapters).
## Payer vs. beneficiary
| Field | Meaning |
| :----------------------------- | :----------------------------------------------------- |
| `contact_id` in `transactions` | The **payer** — who made the payment |
| `contact_id` in `memberships` | The **beneficiary** — who receives the membership |
| `contact_id` in `donations` | The **beneficiary** — who receives the donation credit |
## Getting required IDs
Before creating a bundle, retrieve the IDs valid for your group:
| What you need | Endpoint |
| :----------------------- | :------------------------------------------------------ |
| Allowed payment methods | `GET /v1/transaction_settings` → `payment_method_kinds` |
| Default valid status ID | `GET /v1/transaction_settings` → `default_status_id` |
| All transaction statuses | `GET /v1/transaction_statuses` |
| Membership price IDs | `GET /v1/membership_prices` |
| Donation price IDs | `GET /v1/donation_prices` |
| Campaign codes | `GET /v1/code_campaigns` |
See [Reference data](/pages/v1/reference/fundraising/reference-data) for details on each endpoint.
## Updating and refunds
Bundles are updated via `PATCH /v1/transaction_bundles/{id}`. Updates are **additive**: items not included in the request remain unchanged.
> Always resend all required fields on an item you are updating. Missing required fields may be cleared.
For a **refund**: `PATCH` the transaction with the reimbursed status and `reimbursed_amount`, and also update the `amount` on the linked membership or donation, then `DELETE` or update the products in the bundle so that the sum of the amounts correspond to the amount of the transaction bundle.
See [Update and refund bundles](/pages/v1/reference/fundraising/update-refund-bundles) for step-by-step examples.
## `external_transaction_id`
Use this field to link bundles to your own system's records. Must be a plain **integer**, string are not supported.
# Reference data
Source: https://developers.qomon.com/pages/v1/reference/fundraising/reference-data
Lookup endpoints that provide valid IDs and values for creating transaction bundles.
Before creating a transaction bundle you need group-specific IDs and configuration values. The endpoints below return this reference data.
## Payment method kinds
**Endpoint:** `GET /v1/transaction_settings`
The `payment_method_kinds` array lists the payment method strings allowed for your group. Pass one of these as `payment_method_kind` on each transaction.
The `default_status_id` field gives you the status to use for new valid transactions.
## Transaction statuses
**Endpoint:** `GET /v1/transaction_statuses`
Each status has a `kind` that controls its meaning:
| Kind | Usage |
| :----------- | :------------------------------------------------- |
| `valid` | Normal completed payment |
| `unpaid` | Payment not yet received |
| `reimbursed` | Refunded — pair with `reimbursed_amount` |
| `bank_error` | Payment returned by bank |
| `cancel` | Payment cancelled, the transaction will be ignored |
| `other` | |
Use `default_status_id` from `GET /v1/transaction_settings` for new valid transactions.
## Membership prices
**Endpoint:** `GET /v1/membership_prices`
Returns all membership prices configured for your group. Use the `id` as `membership_price_id` when creating memberships.
Each price includes `amount`, `currency`, `name`, `allow_custom_amount`, and optional `rolling_period` / `duration` fields.
## Membership price groups
**Endpoint:** `GET /v1/membership_price_groups`
Returns membership prices grouped by category. Useful when you want to present prices organised by group (e.g. individual, couple, family) in your integration UI.
## Donation prices
**Endpoint:** `GET /v1/donation_prices`
Returns all donation price tiers configured for your group. Use the `id` as `donation_price_id` when creating donations.
## Campaign codes
**Endpoint:** `GET /v1/code_campaigns`
Returns all campaign codes active for your group. Pass a `code` string as `code_campaign` on a transaction to associate it with a campaign.
# Update and refund bundles
Source: https://developers.qomon.com/pages/v1/reference/fundraising/update-refund-bundles
How to update, partially refund, or cancel a transaction bundle.
## How updates work
`PATCH /v1/transaction_bundles/{id}` is **additive**: items you do not include in the request remain in the bundle unchanged.
* **To update an existing item**: include its `id` plus all required fields. Missing required fields may be cleared to zero/empty values.
* **To add a new item**: omit `id`. A new item is created and appended to the bundle.
* **To leave an item unchanged**: do not include it in the request at all.
> Always resend all required fields when updating an item, not just the fields you want to change.
## Refund
For a **refund** (full or partial)
1. Fetch the bundle to get the transaction `id` and membership/donation `id`:
```
GET /v1/transaction_bundles/{id}
```
2. PATCH with the reimbursed status, `reimbursed_amount`, and the updated `amount` on the linked memberships and donations:
```json theme={"system"}
{
"id": 42,
"data": {
"transactions": [
{
"id": 101,
"contact_id": 123456,
"amount": 2000,
"currency": "eur",
"payment_method_kind": "CB",
"date": "2026-03-29T12:59:52+02:00",
"external_transaction_id": 1001,
"status_id": 2,
"reimbursed_amount": 1000
}
],
"memberships": [
{
"id": 55,
"contact_id": 123456,
"membership_price_id": 10,
"start_date": "2026-01-01T00:00:00Z",
"end_date": "2026-12-31T00:00:00Z",
"amount": 1000,
"amount_initial": 2000,
"currency": "eur"
}
]
}
}
```
Use `GET /v1/transaction_statuses` to find the `id` of the **Reimbursed** status for your group (`kind: reimbursed`).
If a product should deleted, patch the bundle then delete the products.
# Overview
Source: https://developers.qomon.com/pages/v1/reference/search/overview
Query your contacts with flexible AND/OR conditions across contact info, address, financial activity, email engagement, and custom fields.
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.
```text theme={"system"}
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:
```json theme={"system"}
{
"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)*
```json theme={"system"}
{
"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 say | `ope` value |
| ------------------------------------ | ---------------------------------- |
| equals | `eql` |
| does not equal | `not_eql` |
| has a value / is filled in | `ext` |
| has no value / is empty | `not_ext` |
| is before / at most | `lte` |
| is after / at least | `gte` |
| is between two values | `range` — use with `from` and `to` |
| contains the text | `contains` |
| does not contain the text | `not_contains` |
| starts with | `start_with` |
| does not start with | `not_start_with` |
| equals (case-sensitive, exact match) | `eql:strictdata` |
| matches any of a list | `any_of` |
| matches none of a list | `none_of` |
| matches all of a list | `all_of` |
Date fields accept relative values: `"now"`, `"now-30d"`, `"now-1y"`, `"now+1y"`. All dates are interpreted as UTC.
## Pagination and sorting
| Parameter | Description |
| ---------------------- | ---------------------------------------------------------------------------------------------------- |
| `per_page` | Contacts per page — **required**, 1–1000 |
| `page` | Page number (default: 0) |
| `sort_attr` | Sort by: `surname`, `firstname`, `birthdate`, `gender`, `lastchange`, `mail`, `married_name`, `city` |
| `sort_asc` | `true` = ascending order (default: false) |
| `include_interactions` | `true` = 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.
```json theme={"system"}
{ "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.*
```json theme={"system"}
{
"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.*
```json theme={"system"}
{
"data": {
"advanced_search": {
"per_page": 100,
"query": {
"$all": [{
"$all": [
{ "$condition": { "attr": "donations.sum_amount", "ope": "gte", "value": "100" } }
]
}]
}
}
}
}
```
```json theme={"system"}
{
"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.*
```json theme={"system"}
{
"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.*
```json theme={"system"}
{
"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" } }
]
}
]
}
}
}
}
```
*Use `GET /v1/forms/type/{type}` to find your `form_id`, then `GET /forms/{id}` for `form_ref_ids`.*
```json theme={"system"}
{
"data": {
"advanced_search": {
"per_page": 100,
"query": {
"$all": [{
"$all": [{
"$condition": {
"attr": "form",
"form_id": 4530,
"form_ref_ids": [7486],
"ope": "eql",
"value": "consent_email"
}
}]
}]
}
}
}
}
```
## Attribute reference
### Contact fields
| Attribute | Meaning | Operators |
| ----------------- | -------------------------------------- | ---------------------------------------------------------------------------------------- |
| `firstname` | First name | `eql`, `not_eql`, `ext`, `not_ext`, `start_with`, `not_start_with` |
| `surname` | Last name | `eql`, `not_eql`, `ext`, `not_ext`, `start_with`, `not_start_with` |
| `married_name` | Married name | `eql`, `not_eql`, `ext`, `not_ext`, `start_with`, `not_start_with` |
| `gender` | Gender (`M`, `F`, `O`) | `eql`, `not_eql`, `ext`, `not_ext` |
| `birthdate` | Date of birth | `eql`, `ext`, `not_ext`, `range`, `lte`, `gte` |
| `age_cat` | Age category (0–6) | `eql`, `not_eql`, `ext`, `not_ext` |
| `birthcity` | City of birth | `eql`, `not_eql`, `ext`, `not_ext` |
| `birthcountry` | Country of birth | `eql`, `not_eql`, `ext`, `not_ext` |
| `nationality` | Nationality | `eql`, `not_eql`, `ext`, `not_ext` |
| `mail` | Email address | `eql:strictdata`, `not_eql:strictdata`, `ext`, `not_ext`, `start_with`, `not_start_with` |
| `phone` | Landline | `eql`, `not_eql`, `ext`, `not_ext`, `start_with`, `not_start_with` |
| `mobile` | Mobile number | `eql`, `not_eql`, `ext`, `not_ext`, `contains`, `start_with`, `not_start_with` |
| `tags.name` | Tag name | `eql`, `not_eql`, `any_of`, `none_of`, `all_of`, `ext`, `not_ext` |
| `black_list` | Blocked from all communication | `eql`, `not_eql` |
| `lastchange` | Last modification date | `eql`, `ext`, `not_ext`, `range`, `lte`, `gte` |
| `user_id` | ID of last user who edited the contact | `eql`, `not_eql`, `ext`, `not_ext` |
| `user_contact_id` | ID of the linked Qomon user | `ext`, `not_ext` |
| `action_ids` | IDs of linked actions | `eql`, `not_eql` |
### Address fields
| Attribute | Meaning | Operators |
| ------------------------ | -------------------------- | ------------------------------------------------------------------ |
| `address.city` | City | `eql`, `not_eql`, `ext`, `not_ext`, `start_with`, `not_start_with` |
| `address.postalcode` | Postal code | `eql`, `not_eql`, `ext`, `not_ext`, `start_with`, `not_start_with` |
| `address.country` | Country | `eql`, `not_eql`, `ext`, `not_ext` |
| `address.street` | Street name | `eql`, `not_eql`, `ext`, `not_ext`, `start_with`, `not_start_with` |
| `address.housenumber` | House number | `eql`, `not_eql`, `ext`, `not_ext` |
| `address.building` | Building | `eql`, `not_eql`, `ext`, `not_ext` |
| `address.floor` | Floor | `eql`, `not_eql`, `ext`, `not_ext` |
| `address.door` | Door | `eql`, `not_eql`, `ext`, `not_ext` |
| `address.addition` | Address complement | `eql`, `not_eql`, `ext`, `not_ext` |
| `address.pollingstation` | Polling station | `eql`, `not_eql`, `any_of`, `none_of`, `ext`, `not_ext` |
| `address.invalid` | Address flagged as invalid | `eql`, `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.*`)
| Attribute | Meaning | Operators |
| ---------------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `transactions.created_at` | Record creation date | `lte`, `gte`, `range` |
| `transactions.date` | Transaction execution date | `eql`, `not_eql`, `lte`, `gte`, `range` |
| `transactions.amount` | Amount of one transaction (€) | `eql`, `not_eql`, `lte`, `gte`, `range` |
| `transactions.sum_amount` | Total amount across all transactions (€) | `eql`, `not_eql`, `lte`, `gte`, `range` |
| `transactions.count_amount` | Number of transactions | `eql`, `not_eql`, `lte`, `gte`, `range` |
| `transactions.code_campaign` | Campaign code | `ext`, `not_ext`, `eql`, `not_eql`, `contains`, `not_contains`, `start_with`, `not_start_with` |
| `transactions.payment_method_kind` | Payment method | `eql`, `not_eql` |
| `transactions.status_id` | Transaction status ID | `eql`, `not_eql` |
**Donations** (`donations.*`)
| Attribute | Meaning | Operators |
| ----------------------------- | -------------------------- | --------------------------------------- |
| `donations.created_at` | Record creation date | `eql`, `not_eql`, `lte`, `gte`, `range` |
| `donations.transaction_date` | Donation operation date | `eql`, `not_eql`, `lte`, `gte`, `range` |
| `donations.amount` | Amount of one donation (€) | `eql`, `not_eql`, `lte`, `gte`, `range` |
| `donations.sum_amount` | Total donated (€) | `eql`, `not_eql`, `lte`, `gte`, `range` |
| `donations.count_amount` | Number of donations | `eql`, `not_eql`, `lte`, `gte`, `range` |
| `donations.donation_price_id` | Donation type/tier ID | `eql`, `not_eql` |
**Memberships** (`memberships.*`)
| Attribute | Meaning | Operators |
| --------------------------------- | ----------------------------------------------------------- | ----------------------------------------- |
| `memberships.created_at` | Record creation date | `eql`, `not_eql`, `lte`, `gte`, `range` |
| `memberships.start_date` | Membership start date | `eql`, `not_eql`, `lte`, `gte`, `range` |
| `memberships.end_date` | Membership end date | `eql`, `not_eql`, `lte`, `gte`, `range` |
| `memberships.transaction_date` | Membership operation date | `eql`, `not_eql`, `lte`, `gte`, `range` |
| `memberships.period` | Active year — e.g. `"2024"` means active that calendar year | `eql`, `not_eql`, `lte`, `gte`, `range` |
| `memberships.multiple_in_period` | Had more than one membership in a given year | `eql` (value = year string e.g. `"2024"`) |
| `memberships.membership_price_id` | Membership type/tier ID | `eql`, `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`.
| Attribute | Meaning | Operators |
| ------------------------------------ | -------------------------------------- | ---------------------------------------------------------------------------- |
| `emails.created_at` | Email record date | `ext`, `not_ext`, `lte`, `gte`, `range` |
| `emails.type` | Email type | `ext`, `not_ext`, `eql`, `not_eql` |
| `emails.sender` | Sender address | `ext`, `not_ext`, `eql`, `not_eql` |
| `emails.subject` | Email subject line | `ext`, `not_ext`, `contains`, `not_contains`, `start_with`, `not_start_with` |
| `emails.user_id` | ID of Qomon user who sent the campaign | `ext`, `not_ext`, `eql`, `not_eql` |
| `emails.campaign_id` | Campaign ID | `ext`, `not_ext`, `eql`, `not_eql` |
| `emails.campaign_name` | Campaign name | `ext`, `not_ext`, `contains`, `not_contains`, `start_with`, `not_start_with` |
| `emails.campaign_goal` | Campaign goal | `ext`, `not_ext`, `eql`, `not_eql` |
| `emails.campaign_is_successful` | Campaign marked successful | `ext`, `not_ext`, `eql`, `not_eql` |
| `emails.campaign_performance_rating` | Campaign performance score (0–100) | `ext`, `not_ext`, `lte`, `gte`, `range` |
| `emails.template_id` | Template ID | `ext`, `not_ext`, `eql`, `not_eql` |
| `emails.template_name` | Template name | `ext`, `not_ext`, `contains`, `not_contains`, `start_with`, `not_start_with` |
| `emails.delivered_at` | Email delivered date | `ext`, `not_ext`, `lte`, `gte`, `range` |
| `emails.opened_at` | Email opened date | `ext`, `not_ext`, `lte`, `gte`, `range` |
| `emails.clicked_at` | Email clicked date | `ext`, `not_ext`, `lte`, `gte`, `range` |
| `emails.bounced_at` | Email bounced date | `ext`, `not_ext`, `lte`, `gte`, `range` |
| `emails.unsubscribed_at` | Unsubscribed date | `ext`, `not_ext`, `lte`, `gte`, `range` |
| `emails.blacklisted_at` | Blacklisted date | `ext`, `not_ext`, `lte`, `gte`, `range` |
| `emails.abuse_report_at` | Abuse report date | `ext`, `not_ext`, `lte`, `gte`, `range` |
| `emails.clicked_urls` | A URL that was clicked | `ext`, `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).
| Attribute | Meaning | Operators |
| ---------------------- | ------------------------ | --------------------------------------------------------- |
| `sanctions.created_at` | Sanction creation date | `eql`, `not_eql`, `lte`, `gte`, `range` |
| `sanctions.start_date` | Sanction start date | `eql`, `not_eql`, `lte`, `gte`, `range` |
| `sanctions.end_date` | Sanction end date | `eql`, `not_eql`, `ext`, `not_ext`, `lte`, `gte`, `range` |
| `sanctions.status` | Sanction status | `eql`, `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`:
| Attribute | What it searches | Operators |
| --------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `custom_fields` | Custom fields attached to contacts | `ext`, `not_ext`, `eql`, `not_eql`, `contains`, `not_contains`, `lte`, `gte`, `range` |
| `form` | Survey, consent, level of support, presence, or task form answers | `ext`, `not_ext`, `eql`, `not_eql`, `contains`, `not_contains`, `lte`, `gte`, `range` |
```json theme={"system"}
{
"$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_id` — `form_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.
# Overview
Source: https://developers.qomon.com/pages/v1/reference/users/introduction
A user represents a person who can access a Qomon space. User records include personal information such as:
```json theme={"system"}
{
"firstname": "John",
"surname": "Doe",
"mail": "john.doe@example.com",
"mobile": "0123456789",
"address": {
"housenumber": "123",
"street": "Rue de la paix",
"city": "Paris",
"country": "France"
}
}
```
They also include Qomon-specific attributes such as:
```json theme={"system"}
{
"role": "superadmin",
"status": ["available week-end", "available night"]
}
```
The space is determined by the token you use to authenticate the request.
## Roles
A role defines what a user can access and do in a Qomon space. Each user has exactly one role.
| Enum value | Display name | Scope |
| ------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `superadmin` | Administrator | Full access, including platform settings and subscription management. Mandatory role — cannot be deleted. |
| `admin` | Staff | Full access except platform settings, subscription management, and contact exports. Designed for staff or headquarters users. |
| `manager` | Organizer | Limited access to actions, petitions, and teams. No access to the contact database. Designed for field and digital organizers. |
| `user` | Champ | Mobile-app access for field actions. Assigned by default to new users. |
| `custom` | Custom | Permissions defined by your organization's admin. Available on Expert plans only. |
You can manage role permissions and create custom roles only in the Qomon UI, under **Space Settings > General > Role Management**. These actions are not available through the API.
**Tip — retrieving role IDs:** Use `GET /roles` to list the roles available in the current space and retrieve their `role_id` values before calling `PATCH /users/role`.
For more details about Qomon's default roles and permissions, see the [Qomon Help Center article about roles](https://help.qomon.com/fr/articles/8731198-les-roles-par-defaut-sur-qomon).
## Teams
Use teams to organize members of your Qomon space for actions and field organizing. Each team has its own visibility settings.
## Public vs private teams
* **Public teams** are visible to every member of the space and can be returned by `GET /teams/public`.
* **Private teams** are only visible to members who belong to that team or have the `superadmin` role.
`PATCH /teams/{id}` performs a full replacement. It does not partially merge fields.
# Status Page
Source: https://developers.qomon.com/pages/v1/status-page