https://api.[tenant].getomneo.com/api/v3.
How membership is decided
A Profile is a member of a dynamic group when all three of these hold:- It is returned by the group’s candidate query.
- It matches at least one active inclusion rule.
- It matches no active exclusion rule.
- Inclusion rules combine with OR. A Profile passes the inclusion gate when it matches any one of them.
- Exclusion rules are evaluated in order, and the first match wins. As soon as an exclusion rule matches, the Profile is out and no further rules are checked.
- Compose AND and NOT inside a single rule. Put more complex logic in one rule’s JSON Logic expression rather than spreading it across several rules, because separate rules only ever OR (inclusion) or short-circuit (exclusion).
Define the candidate query
Every dynamic group has a candidate query, set through thequery, query_type, and arguments fields on the definition. The query is the first gate: a Profile the query does not return is never evaluated against the rules, so it can never be a member.
| Field | Description |
|---|---|
query | The name of the candidate query to run. |
query_type | A string identifying which query the name refers to. The set of available queries is configured per tenant. |
arguments | An array of { name, value } objects passed to the query. |
Create reusable Rule Definitions
Rules on a group do not carry their own logic. Each rule points at a Group Rule Definition, a reusable template that holds the JSON Logic expression. Create a template once and reference it from as many groups as you like.| Field | Required | Description |
|---|---|---|
handle | Yes | Handle for the template. Handles are globally unique across all Rule Definitions. |
name | Yes | Human-readable name. |
evaluator | Yes | The evaluator for the rule. Use jsonlogic. |
rule | Yes | The JSON Logic object evaluated against a Profile’s data. |
default_arguments | No | A map of { "name": value } defaults, used when a group does not override them. |
is_active | No | Whether the template is active. |
arguments, which override the template’s default_arguments by key. This keeps a single expression, such as “has a given status”, consistent everywhere it is used.
Fields available to a rule
A rule’s JSON Logic is evaluated against this context. Reference the fields withvar, as in the examples on this page.
| Path | Value |
|---|---|
profile.id | The Profile’s id. |
profile.email | The Profile’s email. |
profile.statuses | An array of the Profile’s status handles. |
args.<name> | The rule’s effective arguments: the template’s default_arguments merged with the group rule’s arguments. |
The Rule Definition object
Reading a template withGET /groups/rule-definitions/{id} returns the shared logic and its metadata.
| Field | Description |
|---|---|
id | Template id, referenced by a rule as group_rule_definition_id. |
handle | Globally unique handle. |
name | Human-readable name. |
description | Optional description. |
evaluator | The evaluator. jsonlogic. |
rule | The JSON Logic object evaluated against a Profile. |
default_arguments | {name: value} defaults, used when a group does not override them. |
context_requirements | The context the rule expects to be available when it evaluates. |
meta | Free-form metadata object. |
is_active | Whether new rules may reference the template. |
created_at / updated_at | Timestamps. |
Update or delete a Rule Definition
Templates are shared, so a change ripples to every group that references them.PUT /groups/rule-definitions/{id}updates the template and returns200. Every dynamic group that references it becomes stale: itsrefresh_statusmoves tostaleand it is re-evaluated on the next run.DELETE /groups/rule-definitions/{id}returns204. Referencing groups become stale and their rules are left without logic, so re-point those rules at another template or delete them.- Setting
is_active: falsekeeps existing rules working but blocks new rules from referencing the template.
Attach rules to a group
A Rule is an instance of a template on a specific group. Add one withPOST /groups/definitions/{group}/rules.
| Field | Required | Description |
|---|---|---|
type | Yes | inclusion or exclusion. |
group_rule_definition_id | Yes | The template this rule uses. |
name | No | Optional name for the rule on this group. |
arguments | No | A map of { "name": value } overrides for the template’s default_arguments. |
sort_order | No | Position of the rule. Determines the order exclusion rules are evaluated in. |
arguments.
A rule read returns the template reference, this rule’s overrides, and an effective object:
effective object is the resolved view: the template’s logic merged with this rule’s argument overrides. Use it to confirm exactly what the rule evaluates without cross-referencing the template.
Edit or remove a rule
Update a rule withPUT /groups/definitions/{group}/rules/{rule} to re-point its group_rule_definition_id, or change its arguments, sort_order, or is_active. Remove it with DELETE /groups/definitions/{group}/rules/{rule}, which returns 204. Both mark the group stale so it is re-evaluated on the next run.
Create a dynamic group in one call
You do not have to create the definition and its rules separately. Passtype: "dynamic", the query fields, and a nested rules array where each rule references a group_rule_definition_id.
201 with the definition and its rules. In this example a Profile is a member when the candidate query returns it (profiles lapsed for 90 days), it has the vip status (inclusion), and it does not have the staff status (exclusion).
The definition comes back with the query fields, the refresh schedule, and each rule resolved into effective form:
current_member_count stays 0 on a dynamic definition even after it has members; size the group from its latest snapshot. For the full field reference, see Working with Groups.
The
query and query_type values above are illustrative. The candidate queries available to you are configured per tenant, so use a query that exists in your tenant rather than copying these names.arguments appears at two levels with different shapes. On the definition it configures the candidate query and is an array of { "name", "value" } objects. On a rule, and in a template’s default_arguments, it is a flat { "name": value } map, referenced in JSON Logic as args.<name>.Refresh and read snapshots
Omneo re-evaluates a dynamic group on the schedule set byrefresh_period and refresh_period_type (the same period enum as membership expiry: days, weeks, months, years, and the absolute_* variants). Each evaluation produces a snapshot that records the per-Profile result.
Start a run on demand:
202: the run has been accepted and will complete shortly. It does not return the new membership inline.
Read the runs, newest first, and drill into a single run’s per-Profile detail:
status completed; a run that could not finish has status failed with an error.
| Field | Description |
|---|---|
id | Snapshot id. |
group_definition_id | The group this run evaluated. |
status | Lifecycle state of the run. A finished run is completed; a run that could not finish is failed. |
source | manual for an on-demand run, scheduled for a run the refresh cadence started. |
snapshot_at | The point in time the run represents. |
started_at / completed_at / failed_at | When the run started, completed, or failed. Unused ones are null. |
error | Failure message when status is failed, otherwise null. |
total_candidates | Profiles the candidate query returned. |
total_excluded | Candidates removed by an exclusion rule. |
total_profiles | Final member count for the run. There is no separate matched count; this is the number in the group. |
inclusion_breakdown / exclusion_breakdown | Per-rule summary of the run, keyed by rule id. Informational; use the total_* fields for exact counts. |
duration_ms | Run duration in milliseconds. |
meta | Free-form metadata object. |
created_at / updated_at | Timestamps. |
A
POST to /snapshots returns 202 with this same record before it finishes, so completed_at and total_profiles are not yet populated. Poll the snapshot until status is completed.filter[result_status] and filter[processing_status].
| Field | Description |
|---|---|
id | Row id. |
group_definition_id / group_snapshot_id | The group and the run this row belongs to. |
profile_id | The evaluated Profile. |
processing_status | Whether this Profile finished evaluating in the run. Filterable with filter[processing_status]. |
result_status | The outcome for this Profile, for example included or excluded. Filterable with filter[result_status]. |
matched_group_rule_ids | The inclusion rule ids that matched. |
excluded_by_group_rule_id | The exclusion rule id that removed the Profile, or null. |
evaluation_error | An error captured while evaluating this Profile, otherwise null. |
processed_at | When this Profile was evaluated. |
created_at / updated_at | Timestamps. |
100) but an exclusion rule (101) removed it, so it is not a member.
The current membership of a dynamic group is its most recent completed snapshot. Reading members through GET /groups/definitions/{group}/profiles (covered in Working with Groups) returns exactly that set.
The refresh_status field tells you where the group is in this cycle.
refresh_status | Meaning |
|---|---|
fresh | The latest run completed and membership is up to date. |
stale | The query or rules changed, so the group is due for re-evaluation. |
refreshing | A run is in progress. |
failed | The last run did not complete. |
stale and schedules it for the next run. Start a snapshot run yourself if you need membership updated before the schedule fires.
Debug membership with explain
To understand why a single Profile is or is not a member without reading a whole snapshot, call the explain endpoint.200 and reports the outcome of the three gates for that Profile: whether the candidate query returned it, which inclusion rules matched, and whether an exclusion rule removed it.
| Field | Description |
|---|---|
profile_id | The Profile explained. |
query.matched | Whether the candidate query returned the Profile. |
inclusion.matched | Whether at least one inclusion rule matched. |
inclusion.matches[] | Each inclusion rule evaluated, with its rule_id, handle, name, matched, and an explanation holding the evaluator and its result. |
exclusion.matched | Whether an exclusion rule removed the Profile. |
exclusion.excluded_by | The exclusion rule that removed the Profile, or null. |
in_group | Whether the Profile is a member. |
reason | A summary code for the outcome (see below). |
reason | Meaning |
|---|---|
not_in_query | The candidate query did not return the Profile, so the rules were never evaluated. |
no_inclusion_match | The query returned the Profile, but no inclusion rule matched. |
excluded_by_rule | An exclusion rule matched and removed the Profile. |
included | The Profile passed all three gates and is a member. |
evaluation_error | A rule could not be evaluated for the Profile. |
exclusion.matched is true and excluded_by names the rule:
reason tells you which gate to look at, and the per-rule detail shows which inclusion or exclusion rule was responsible.
Worked example: lapsed VIPs, excluding staff
This builds the group used throughout this guide from scratch.Create the reusable template
POST /groups/rule-definitions with a jsonlogic rule that checks a status. The response is 201 with the template id, for example 12. Reuse it for both the inclusion and exclusion rule by passing different arguments.Create the group and rules in one call
POST /groups/definitions with type: "dynamic", the candidate query, refresh_period, and a rules array referencing template 12 (inclusion, status: "vip") and template 20 (exclusion, status: "staff"). The response is 201 with the definition and its rules.Run the first snapshot
POST /groups/definitions/42/snapshots returns 202. It requires at least one active inclusion rule, so it would return 422 on a group with none.Read the result
GET /groups/definitions/42/snapshots lists runs newest first. When status is completed, total_profiles is the member count. GET /groups/definitions/42/profiles returns the members as group_snapshot_profile rows.Debug a missing Profile
If a customer you expected is absent,
GET /groups/definitions/42/profiles/{profile}/explain returns the reason: not_in_query means the candidate query did not return them, no_inclusion_match means no inclusion rule matched, and excluded_by_rule names the exclusion rule that removed them.Errors
| Status | When |
|---|---|
200 | A rule or Rule Definition update succeeded. |
201 | A dynamic definition or a rule was created. |
202 | A snapshot run was accepted and will complete shortly. |
204 | A rule or Rule Definition was deleted. |
401 | Missing or invalid bearer token. |
403 | The token lacks the scope or permission for this group. |
404 | The group does not exist, or the rule or snapshot is not under the group named in the URL. |
422 | Validation failed. For example, starting a snapshot on a group with no active inclusion rule, sending membership-expiry fields (period, absolute_expiry) to a dynamic group, or trying to add or remove members manually. |
Edge cases and gotchas
- The candidate query is the first gate. A Profile the query does not return can never be a member, whatever the rules say. Check
query.matchedin explain before the rules. - Inclusion is OR, exclusion is first-match. Separate rules only ever OR (inclusion) or short-circuit (exclusion). Put AND and NOT logic inside a single rule’s JSON Logic.
- Changes go stale, not live. Editing the query, a rule, or a referenced template marks the group
stale. Membership updates on the next scheduled run, or immediately if you start a snapshot. - A snapshot needs an inclusion rule. Starting a run on a group with no active inclusion rule returns
422. - Dynamic groups reject manual and expiry fields. Manual add or remove, and membership-expiry fields such as
periodandabsolute_expiry, return422on a dynamic group. current_member_countstays0. Size a dynamic group fromtotal_profileson its latest completed snapshot.- Deleting a template breaks its rules. A deleted Rule Definition leaves referencing rules without logic. Re-point them at another template or delete them.
Related
- Groups concept
- Working with Groups
- Favourite groups
- Group Rule API
- Add Group Rule API
- Edit Group Rule API
- Delete Group Rule API
- Browse Group Rule Definitions API
- Add Group Rule Definition API
- Update Group Rule Definition API
- Delete Group Rule Definition API
- Start a manual snapshot run API
- List snapshots API
- Explain group membership API