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.
FieldDescription
queryThe name of the candidate query to run.
query_typeA string identifying which query the name refers to. The set of available queries is configured per tenant.
argumentsAn array of { name, value } objects passed to the query.
A dynamic group must have a candidate query. You can change the query later, but you cannot remove it.

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.
FieldRequiredDescription
handleYesHandle for the template. Handles are globally unique across all Rule Definitions.
nameYesHuman-readable name.
evaluatorYesThe evaluator for the rule. Use jsonlogic.
ruleYesThe JSON Logic object evaluated against a Profile’s data.
default_argumentsNoA map of { "name": value } defaults, used when a group does not override them.
is_activeNoWhether the template is active.
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.
PathValue
profile.idThe Profile’s id.
profile.emailThe Profile’s email.
profile.statusesAn 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 with GET /groups/rule-definitions/{id} returns the shared logic and its metadata.
FieldDescription
idTemplate id, referenced by a rule as group_rule_definition_id.
handleGlobally unique handle.
nameHuman-readable name.
descriptionOptional description.
evaluatorThe evaluator. jsonlogic.
ruleThe JSON Logic object evaluated against a Profile.
default_arguments{name: value} defaults, used when a group does not override them.
context_requirementsThe context the rule expects to be available when it evaluates.
metaFree-form metadata object.
is_activeWhether new rules may reference the template.
created_at / updated_atTimestamps.

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.
FieldRequiredDescription
typeYesinclusion or exclusion.
group_rule_definition_idYesThe template this rule uses.
nameNoOptional name for the rule on this group.
argumentsNoA map of { "name": value } overrides for the template’s default_arguments.
sort_orderNoPosition of the rule. Determines the order exclusion rules are evaluated in.
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.
FieldDescription
idSnapshot id.
group_definition_idThe group this run evaluated.
statusLifecycle state of the run. A finished run is completed; a run that could not finish is failed.
sourcemanual for an on-demand run, scheduled for a run the refresh cadence started.
snapshot_atThe point in time the run represents.
started_at / completed_at / failed_atWhen the run started, completed, or failed. Unused ones are null.
errorFailure message when status is failed, otherwise null.
total_candidatesProfiles the candidate query returned.
total_excludedCandidates removed by an exclusion rule.
total_profilesFinal member count for the run. There is no separate matched count; this is the number in the group.
inclusion_breakdown / exclusion_breakdownPer-rule summary of the run, keyed by rule id. Informational; use the total_* fields for exact counts.
duration_msRun duration in milliseconds.
metaFree-form metadata object.
created_at / updated_atTimestamps.
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].
FieldDescription
idRow id.
group_definition_id / group_snapshot_idThe group and the run this row belongs to.
profile_idThe evaluated Profile.
processing_statusWhether this Profile finished evaluating in the run. Filterable with filter[processing_status].
result_statusThe outcome for this Profile, for example included or excluded. Filterable with filter[result_status].
matched_group_rule_idsThe inclusion rule ids that matched.
excluded_by_group_rule_idThe exclusion rule id that removed the Profile, or null.
evaluation_errorAn error captured while evaluating this Profile, otherwise null.
processed_atWhen this Profile was evaluated.
created_at / updated_atTimestamps.
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.
refresh_statusMeaning
freshThe latest run completed and membership is up to date.
staleThe query or rules changed, so the group is due for re-evaluation.
refreshingA run is in progress.
failedThe last run did not complete.
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.
FieldDescription
profile_idThe Profile explained.
query.matchedWhether the candidate query returned the Profile.
inclusion.matchedWhether 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.matchedWhether an exclusion rule removed the Profile.
exclusion.excluded_byThe exclusion rule that removed the Profile, or null.
in_groupWhether the Profile is a member.
reasonA summary code for the outcome (see below).
reasonMeaning
not_in_queryThe candidate query did not return the Profile, so the rules were never evaluated.
no_inclusion_matchThe query returned the Profile, but no inclusion rule matched.
excluded_by_ruleAn exclusion rule matched and removed the Profile.
includedThe Profile passed all three gates and is a member.
evaluation_errorA rule could not be evaluated for the Profile.
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

StatusWhen
200A rule or Rule Definition update succeeded.
201A dynamic definition or a rule was created.
202A snapshot run was accepted and will complete shortly.
204A rule or Rule Definition was deleted.
401Missing or invalid bearer token.
403The token lacks the scope or permission for this group.
404The group does not exist, or the rule or snapshot is not under the group named in the URL.
422Validation 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.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.