Segments

Named groups of organizations on revenexx — hand-picked, rule-materialised, or both, with the rule language over organization columns and the buying-metrics projection.

A segment is a named group of organizations. Segments group companies, never people — a person is reached through their organization.

Membership comes from either side and the two coexist:

  • hand-picked — a segment_members row with source: "manual";
  • rule-materialised — rows with source: "rule", written by a recompute.

A recompute only ever inserts and deletes rule rows, so a hand-picked member survives every rule change.

The segment

Request
curl "https://api.revenexx.com/v1/customers/segments?limit=50" \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..."
FieldMeaning
codeStable identifier, unique per tenant — what other apps and integrations name the segment by. Lowercase with underscores is the convention.
labelsLocalized display names. Null means a client falls back to showing the code.
positionSort order, ascending.
rulesThe selector. Null means the segment is manual-only.
rule_matchall (AND, default) or any (OR). Null means the same as all.
rules_computed_atWhen the rule last finished a complete recompute. Null after a rule change and while a chunked recompute is still running — so it doubles as "are the rule memberships trustworthy right now?".

Membership by hand

Request
curl -X POST https://api.revenexx.com/v1/customers/segment_members \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{"segment_id":"…","organization_id":"…","source":"manual"}'

DELETE /v1/customers/segment_members/{id} takes a company out again.

The rule language

Same language product categories use, evaluated over organization columns, setting:<key> entries, and the organization_metrics projection — so "no order in 365 days" is expressible without joining the orders app.

Rule
{
  "target": "organizations",
  "rule_match": "all",
  "conditions": [
    { "field": "revenue_365d", "operator": "gte", "value": 100000 },
    { "field": "status", "operator": "eq", "value": "active" }
  ]
}

target accepts only organizations; any other value is rejected. Between 1 and 25 conditions.

Fields

What the organization is — an organizations column: name, status, vat_id, branche, external_team_id. Or a top-level key of the free-form settings blob, as setting:<key>.

What it did — read from the buying-metrics projection:

text
order_count          order_count_30d      order_count_90d      order_count_365d
revenue_total        revenue_30d          revenue_90d          revenue_365d
avg_order_value      avg_order_value_365d
first_order_at       last_order_at        currency
days_since_last_order

days_since_last_order is virtual: it takes gt/gte/lt/lte only, compares last_order_at against a cut-off computed at evaluation time, and never matches an organization that never ordered. For those, use last_order_at with is_empty.

Operators

eq, neq, gt, gte, lt, lte, in (array), contains, starts_with, ends_with, is_empty, is_not_empty. The last two take no value.

A number or boolean makes a setting: condition compare as JSON, so it only matches values stored as a JSON number or boolean — not the string "true".

branche and ?branche= match exactly and case-sensitively.

Two traps in the metrics

  • The projection is materialized. A rule reads what the last refresh stored, not live orders. Refresh before you trust a threshold.
  • currency_mixed. A company that ordered in more than one currency still has stored sums, but they are not comparable against a threshold. A revenue rule should say so.

Preview before you store

Request
curl -X POST "https://api.revenexx.com/v1/customers/segments/{segment_id}/rules/preview" \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{"conditions":[{"field":"revenue_365d","operator":"gte","value":100000}],"target":"organizations"}'

A dry run: how many organizations the rule would select, with a handful by name. It writes nothing, and it evaluates the rule in the request body rather than the stored one, so an unsaved rule can be previewed.

One count query for the common single-query rule. any rules and rules repeating a column are combined in the app and capped at 5000 idscapped: true means count is a lower bound.

Recompute is chunked

Request
curl -X POST "https://api.revenexx.com/v1/customers/segments/{segment_id}/rules/recompute" \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{}'

Evaluates segments.rulesnot the request body — inserts newly matching organizations as source: "rule", and deletes rule rows that no longer match.

Loop until done is true. The run is bounded by a wall-clock budget. While done is false, POST again with the returned cursor. added/removed/processed count this call only. rules_computed_at is stamped only when a pass completes.Omitting cursor resumes an unfinished pass and starts a fresh one after a completed pass; an explicit null always restarts.

Changing a rule does not move a single membership. Run the recompute.

POST /v1/customers/segments/rules/recompute-all applies the same sync to every segment with a non-null rule. A failing segment is reported in its own result entry instead of aborting the run; the run shares one budget, so repeat until the top-level done is true.

Where to go next

  • Organizations — the columns and metrics a rule reads.
  • Categories — the same rule language over products.
  • Price lists — buyer-scoped pricing, which is organization- and contact-scoped rather than segment-scoped.
Was this page helpful?