Audience segments
An audience segment is a stored filter over the merchant's contact book — "everyone on the newsletter list who came from the storefront import", "everyone tagged vip who hasn't unsubscribed from email". Where a contact list is static membership you mutate, a segment re-evaluates on every preview and every broadcast send. This page covers ripllo.audienceSegments on the Node SDK. For the underlying HTTP surface and the rule grammar, see API → Audience segments.
Namespace
ripllo.audienceSegments.list()
ripllo.audienceSegments.get(id)
ripllo.audienceSegments.create(input)
ripllo.audienceSegments.update(id, patch)
ripllo.audienceSegments.delete(id)
ripllo.audienceSegments.preview(id, input?)
ripllo.audienceSegments.previewAdhoc(input)
Standard CRUD plus two preview helpers. preview runs the segment by ID; previewAdhoc runs a filter that hasn't been saved yet (used by the dashboard's segment-builder UI to show live counts as you tweak the query).
The filter shape
Every write on this resource carries one filter object:
filter: {
match: 'all' | 'any', // required — 'all' = AND, 'any' = OR
rules: [ // up to 20 rules; a flat array, not a tree
{ field: '…', op: '…', value: … },
],
}
There is no top-level rules: { all: [...] } wrapper and no nesting — match and rules are siblings inside filter, and a body shaped the other way is rejected with 400 VALIDATION.
Fields and the operators each one accepts:
field |
Operators | value |
|---|---|---|
list |
in, not_in |
Array of contact-list IDs |
tag |
in, not_in |
Array of tag names |
subscription.<channel> |
eq, neq |
The state for that channel — subscribed, unsubscribed, pending or bounced. Channels are email, sms, whatsapp, … |
createdAt |
gte, lte, gt, lt |
ISO 8601 date string (compared against Contact.createdAt) |
source |
eq, neq, in, not_in |
Source string such as storlaunch_customer, manual_import, form_signup — an array for the *_in forms |
The full operator enum is in, not_in, eq, neq, gte, lte, gt, lt. equals, not_equals, contains, not_contains, exists, between and within_days do not exist — a rule using one is rejected at validation.
A rule the resolver doesn't understand is dropped, not rejected. Validation only checks the
openum and the shape; the field/operator pairing above is applied later, and any combination outside the table (sayfield: 'tags'instead of'tag', orcreatedAtwitheq) resolves to nothing and is silently skipped. If every rule drops out, the segment matches every contact in the workspace. Alwayspreviewa new segment and sanity-check the count before you point a send at it.
Methods
audienceSegments.create
Signature. ripllo.audienceSegments.create(input): Promise<Segment>
const seg = await ripllo.audienceSegments.create({
name: 'VIP — newsletter',
description: 'High-value buyers who are still subscribed to email.',
filter: {
match: 'all',
rules: [
{ field: 'tag', op: 'in', value: ['vip'] },
{ field: 'list', op: 'in', value: ['<newsletter-list-id>'] },
{ field: 'subscription.email', op: 'eq', value: 'subscribed' },
],
},
});
name is unique per workspace — a duplicate returns 409 NAME_EXISTS. The cached size is refreshed in the background right after create.
audienceSegments.list
Signature. ripllo.audienceSegments.list(): Promise<{ segments: Segment[] }>
Returns every segment in the workspace, most-recently-updated first. No pagination — segment counts are typically small. This router does wrap its payload in { segments } (contact lists and channels do not — check per resource).
audienceSegments.get
Signature. ripllo.audienceSegments.get(id): Promise<Segment>
Fetches one segment by ID (an opaque cuid), including cachedSize and cachedAt from the last resolve.
audienceSegments.update
Signature. ripllo.audienceSegments.update(id, patch): Promise<Segment>
PATCH semantics over name, description and filter. Pass filter to replace the whole thing — filters aren't deep-merged, and a partial filter (e.g. rules without match) fails validation. Changing filter kicks off a cachedSize refresh.
await ripllo.audienceSegments.update('<segment-id>', {
filter: {
match: 'all',
rules: [
{ field: 'tag', op: 'in', value: ['vip'] },
{ field: 'createdAt', op: 'lt', value: '2026-01-01T00:00:00Z' },
],
},
});
audienceSegments.delete
Signature. ripllo.audienceSegments.delete(id): Promise<{ id: string; deleted: boolean }>
Hard delete. Nothing rewrites broadcasts that referenced the segment — a broadcast whose audience.segmentIds names a deleted segment simply resolves fewer contacts, and fails with EMPTY_AUDIENCE at send time if that leaves it with none. Clean up the references yourself.
audienceSegments.preview
Signature. ripllo.audienceSegments.preview(id, input?): Promise<{ count: number; sample: ContactSample[] }>
Runs the saved segment and returns a total count plus the first 20 matching contacts.
const preview = await ripllo.audienceSegments.preview('<segment-id>');
console.log(preview.count);
for (const c of preview.sample) console.log(c.email);
The sample is fixed at 20 — the input argument is accepted by the SDK but ignored by the server, so there is no way to widen it. Each sample entry is { id, email, phone, firstName, lastName }.
Preview does not refresh the segment's cachedSize; only create and a filter update do that.
audienceSegments.previewAdhoc
Signature. ripllo.audienceSegments.previewAdhoc(input): Promise<{ count: number }>
Runs an unsaved filter — the dashboard's segment-builder calls this while you edit to show "this matches 512 contacts". The body must wrap the filter under a filter key, and the response carries a count only, no sample.
const { count } = await ripllo.audienceSegments.previewAdhoc({
filter: {
match: 'any',
rules: [
{ field: 'tag', op: 'in', value: ['vip'] },
{ field: 'source', op: 'in', value: ['storlaunch_customer', 'form_signup'] },
],
},
});
Debounce on the client side — each call is a full COUNT over the contact table.
Types
interface Segment {
id: string; // opaque cuid — no 'as_' prefix
accountId: string;
name: string;
description: string | null;
filter: SegmentFilter;
cachedSize: number; // last resolved size; 0 until first refresh
cachedAt: string | null;
createdAt: string;
updatedAt: string;
}
interface SegmentFilter {
match: 'all' | 'any';
rules: SegmentRule[]; // max 20
}
interface SegmentRule {
field: 'list' | 'tag' | `subscription.${string}` | 'createdAt' | 'source';
op: 'in' | 'not_in' | 'eq' | 'neq' | 'gte' | 'lte' | 'gt' | 'lt';
value?: unknown;
}
Common patterns
Build a "signed up before 2026" segment
await ripllo.audienceSegments.create({
name: 'Pre-2026 subscribers',
filter: {
match: 'all',
rules: [
{ field: 'createdAt', op: 'lt', value: '2026-01-01T00:00:00Z' },
{ field: 'subscription.email', op: 'eq', value: 'subscribed' },
],
},
});
Date rules compare Contact.createdAt — there is no purchase-recency field in the v1 grammar, so "lapsed buyer" style targeting has to be expressed through tags or lists you maintain yourself.
Use a segment as a broadcast audience
const seg = await ripllo.audienceSegments.create({ name: 'May target', filter });
await ripllo.broadcasts.create({
name: 'May promo',
providers: ['email_resend'],
content: { email: { subject: 'May promo', html: '…', text: '…' } },
audience: { segmentIds: [seg.id] },
});
The broadcast re-runs the segment at send time; if you want a frozen audience, copy the previewed contacts into a static list first. Resolution is capped at 50,000 contacts per segment.
Live builder UI
let debounce: NodeJS.Timeout;
function onFilterChange(filter: SegmentFilter) {
clearTimeout(debounce);
debounce = setTimeout(async () => {
const { count } = await ripllo.audienceSegments.previewAdhoc({ filter });
setMatchCount(count);
}, 200);
}
Errors
| Code | Status | Cause |
|---|---|---|
VALIDATION |
400 | Missing filter/match, more than 20 rules, or an op outside the eight-value enum. |
NAME_EXISTS |
409 | A segment with that name already exists in the workspace. |
NOT_FOUND |
404 | Segment doesn't exist in this workspace. |
NO_ACCOUNT |
403 | Credential carries no accountId. |
See Errors for handling.
Next
- Contacts — the source data segments query against.
- Broadcasts — the send-time consumer.
- API → Audience segments — full rule-language reference.