Segments
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_membersrow withsource: "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
curl "https://api.revenexx.com/v1/customers/segments?limit=50" \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..."
| Field | Meaning |
|---|---|
code | Stable identifier, unique per tenant — what other apps and integrations name the segment by. Lowercase with underscores is the convention. |
labels | Localized display names. Null means a client falls back to showing the code. |
position | Sort order, ascending. |
rules | The selector. Null means the segment is manual-only. |
rule_match | all (AND, default) or any (OR). Null means the same as all. |
rules_computed_at | When 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
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.
{
"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:
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
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 ids — capped: true means count is a lower bound.
Recompute is chunked
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.rules — not the request body — inserts newly matching organizations as source: "rule", and deletes rule rows that no longer match.
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.