Skip to main content
A dynamic group computes its own membership. You define a candidate query that selects the Profiles to consider, attach inclusion and exclusion rules that decide which of those Profiles belong, and Omneo re-evaluates the group on a schedule. This guide covers the membership model, building the query and rules, creating a dynamic group in one call, refreshing it, and debugging why a Profile is or is not a member. For the model behind groups, see the Groups concept. All endpoints are bearer authenticated and live under 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:
  1. It is returned by the group’s candidate query.
  2. It matches at least one active inclusion rule.
  3. It matches no active exclusion rule.
The rules combine in a fixed way:
  • 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 the query, 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. The candidate query is the same mechanism automations use, configured the same way and drawn from the same set. For the full list of queries and their arguments, see Automation Queries. A dynamic group must have a candidate query. You can change the query later, but you cannot remove it. The query must return profiles, since membership is a set of profiles. Most queries do; ones that return other objects (achievementProgress, expiredReservations, lastStockChangeList, pointBalance, tierAchieved, unpurchasedListItems) are valid in an automation but rejected here.

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.
Because the logic lives on the template, a group that references it adjusts behaviour only through 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 with var, as in the examples on this page.

The Rule Definition object

Reading a template with GET /groups/rule-definitions/{id} returns the shared logic and its metadata.

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 returns 200. Every dynamic group that references it becomes stale: its refresh_status moves to stale and it is re-evaluated on the next run.
  • DELETE /groups/rule-definitions/{id} returns 204. 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: false keeps 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 with POST /groups/definitions/{group}/rules.
A rule references a template and cannot carry its own logic. To change what a rule tests, point it at a different template or adjust its arguments. A rule read returns the template reference, this rule’s overrides, and an effective object:
The 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 with PUT /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. Pass type: "dynamic", the query fields, and a nested rules array where each rule references a group_rule_definition_id.
The response is 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 by refresh_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:
The response is 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:
Each run returns a snapshot record. A finished run has status completed; a run that could not finish has status failed with an error.
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.
Drill into a run’s per-Profile rows with the profiles endpoint above. Narrow the list with filter[result_status] and filter[processing_status].
The second row shows the model in action: the Profile matched an inclusion rule (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. Editing the query or the rules marks the group 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.
The response is 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.
Read it in gate order. For a Profile removed by an exclusion rule, exclusion.matched is true and excluded_by names the rule:
Use explain when a Profile is missing from a group you expected it in: the 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.
1

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.
2

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.
3

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.
4

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.
5

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

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.matched in 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 period and absolute_expiry, return 422 on a dynamic group.
  • current_member_count stays 0. Size a dynamic group from total_profiles on 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.