Docs Reference

Segments reference

Rule JSON, targets, operators, every field and the built-in templates.

A segment is a saved rule set over session_facts, the one-row-per-session table. Every segment compiles to a single SQL statement, so "watched 50% of the VSL but did not opt in" is literally max_video_pct >= 50 AND is_lead = false. The dashboard builder, the REST API (POST /segments, POST /segments/preview) and the MCP tools (create_segment, preview_segment) all take the same JSON.

1. Rule JSON#

{
  "target": "visitor",
  "window_days": 30,
  "site_id": "optional-site-uuid",
  "funnel_id": "optional-funnel-uuid",
  "all": [
    { "field": "max_video_pct", "op": ">=", "value": 50 },
    { "field": "is_lead", "op": "=", "value": false },
    { "any": [
      { "field": "clicked", "op": "contains", "value": "cta_pricing" },
      { "field": "max_scroll_pct", "op": ">=", "value": 75 }
    ]}
  ]
}
type SegmentTarget = 'session' | 'visitor' | 'person'
type Op = '=' | '!=' | '>' | '>=' | '<' | '<=' | 'contains' | 'not_contains' | 'in' | 'not_in' | 'exists' | 'not_exists' | 'starts_with'
type Condition = { field: string; op: Op; value?: unknown }
type Group = { all: Node[] } | { any: Node[] } | { not: Node }
type Node = Condition | Group
type SegmentRules = { target: SegmentTarget; window_days: number; site_id?: string; funnel_id?: string } & Group
  • all = AND, any = OR, not = negation. Groups nest without limit. An empty all matches everything, an empty any matches nothing.
  • window_days (1..3650) limits sessions to started_at >= now() - window.
  • site_id restricts to one site; funnel_id to sessions that reached any step of that funnel.
  • Test sessions (is_test = true) are always excluded; this is not configurable.

2. Targets and aggregation#

target Returns How conditions are evaluated
session session_id directly on each session row
visitor visitor_id sessions grouped per visitor; conditions on aggregates (HAVING)
person person_id same, grouped per identified person (person_id IS NOT NULL)

Aggregation per field type when target is visitor or person:

Type Aggregate Example
number MAX max_video_pct >= 50 → the visitor's best session reached 50%
timestamp MAX (latest) lead_at >= '2026-01-01'
boolean BOOL_OR is_lead = false → never became a lead in any session
string set semantics (any session) utm_source = 'facebook' → at least one session came from facebook; != / not_* mean "no session matched"
string_array union across sessions clicked contains 'cta_pricing' → clicked it in any session
json any session video_by_id contains 'vsl_main' → has a key

session_count is the number of sessions per visitor/person inside the window and is only valid for those targets.

3. Operators by type#

Type Operators Value
number = != > >= < <= in not_in exists not_exists number; in takes an array of numbers
boolean = != exists not_exists boolean
string = != contains not_contains starts_with in not_in exists not_exists string; contains/starts_with are case-insensitive
string_array contains not_contains in not_in exists not_exists contains one string; in = overlaps with any of the array
timestamp = != > >= < <= exists not_exists ISO-8601 string or epoch ms
json contains exists not_exists contains = has top-level key

exists / not_exists take no value. Empty strings count as not set.

4. Fields#

Field Type Operators Meaning
session_id string = != contains not_contains starts_with in not_in exists not_exists Session id (uuid)
site_id string = != contains not_contains starts_with in not_in exists not_exists Site id (uuid)
visitor_id string = != contains not_contains starts_with in not_in exists not_exists Visitor id (uuid); null in anonymous mode
person_id string = != contains not_contains starts_with in not_in exists not_exists Person id (uuid) once the visitor identified
started_at timestamp = != > >= < <= exists not_exists Session start
ended_at timestamp = != > >= < <= exists not_exists Session end (set when the session is closed)
active_seconds number = != > >= < <= in not_in exists not_exists Seconds the tab was visible and the user active
total_seconds number = != > >= < <= in not_in exists not_exists Wall-clock seconds between first and last event
page_count number = != > >= < <= in not_in exists not_exists Pageviews in the session
landing_url string = != contains not_contains starts_with in not_in exists not_exists Full URL of the first page
landing_path string = != contains not_contains starts_with in not_in exists not_exists Path of the first page, e.g. /vsl
exit_url string = != contains not_contains starts_with in not_in exists not_exists Last URL seen
device_type string = != contains not_contains starts_with in not_in exists not_exists desktop
browser string = != contains not_contains starts_with in not_in exists not_exists Browser family
os string = != contains not_contains starts_with in not_in exists not_exists Operating system
country string = != contains not_contains starts_with in not_in exists not_exists ISO country code from the edge
region string = != contains not_contains starts_with in not_in exists not_exists Region/state from the edge
consent_mode string = != contains not_contains starts_with in not_in exists not_exists auto
consent_granted_at timestamp = != > >= < <= exists not_exists When consent was granted, if ever
utm_source string = != contains not_contains starts_with in not_in exists not_exists utm_source of the session
utm_medium string = != contains not_contains starts_with in not_in exists not_exists utm_medium
utm_campaign string = != contains not_contains starts_with in not_in exists not_exists utm_campaign
utm_content string = != contains not_contains starts_with in not_in exists not_exists utm_content
utm_term string = != contains not_contains starts_with in not_in exists not_exists utm_term
fbclid string = != contains not_contains starts_with in not_in exists not_exists Meta click id
gclid string = != contains not_contains starts_with in not_in exists not_exists Google click id
ttclid string = != contains not_contains starts_with in not_in exists not_exists TikTok click id
referrer string = != contains not_contains starts_with in not_in exists not_exists Document referrer of the first page
max_scroll_pct number = != > >= < <= in not_in exists not_exists Max scroll depth on the landing page, 0..100
scroll_by_path json contains exists not_exists Max scroll per path, e.g. {"/": 75}
max_video_pct number = != > >= < <= in not_in exists not_exists Max video progress across all videos, 0..100
video_by_id json contains exists not_exists Per-video progress, e.g. {"vsl_main": {"max_pct": 52}}
quiz_id string = != contains not_contains starts_with in not_in exists not_exists Quiz started in this session
quiz_total_steps number = != > >= < <= in not_in exists not_exists Declared number of quiz steps
quiz_last_step number = != > >= < <= in not_in exists not_exists Highest quiz step reached (1-based)
quiz_completed boolean = != exists not_exists Quiz finished
quiz_answers json contains exists not_exists Answers by step, e.g. {"1": "B"}
clicked string_array contains not_contains in not_in exists not_exists data-track names clicked, e.g. {cta_hero,faq_3}
forms_started string_array contains not_contains in not_in exists not_exists data-track-form names focused
forms_submitted string_array contains not_contains in not_in exists not_exists data-track-form names submitted
funnel_steps_reached json contains exists not_exists {"": max_step_index}
is_lead boolean = != exists not_exists Identified with email/phone (lead)
lead_at timestamp = != > >= < <= exists not_exists When the lead was captured
is_booked boolean = != exists not_exists A booking (Calendly or booked_call outcome) was attributed to this session
booked_at timestamp = != > >= < <= exists not_exists When the booking happened
is_customer boolean = != exists not_exists A purchase outcome was attributed to this session
revenue_cents number = != > >= < <= in not_in exists not_exists Revenue attributed to this session, in cents
custom json contains exists not_exists Everything sent via rf.set({...}); query with custom.
session_count number (visitor/person only) = != > >= < <= in not_in exists not_exists Number of sessions of the visitor/person inside the window (visitor/person targets only)

Custom fields#

Anything sent with rf.set({ plan_interest: 'premium', score: 42, vip: true }) lands in session_facts.custom and is queryable as custom.<key> (keys: [a-z0-9_]{1,64}). The type is inferred from the value in the condition:

{ "field": "custom.plan_interest", "op": "=", "value": "premium" }
{ "field": "custom.score", "op": ">=", "value": 40 }
{ "field": "custom.vip", "op": "=", "value": true }
{ "field": "custom.plan_interest", "op": "exists" }

5. Built-in templates#

  • vsl_25_no_lead — VSL 25% – no lead (visitor, 30 days; sync: meta): Visitors in the last 30 days where max video % ≥ 25 and is lead = false
  • vsl_50_no_lead — VSL 50% – no lead (visitor, 30 days; sync: meta): Visitors in the last 30 days where max video % ≥ 50 and is lead = false
  • vsl_75_no_lead — VSL 75% – no lead (visitor, 30 days; sync: meta): Visitors in the last 30 days where max video % ≥ 75 and is lead = false
  • quiz_abandoners — Quiz abandoners (visitor, 30 days; sync: meta): Visitors in the last 30 days where quiz last step ≥ 1 and quiz completed = false and is lead = false
  • high_intent_no_lead — High intent – no lead (visitor, 30 days; sync: meta): Visitors in the last 30 days where max scroll % ≥ 75 and clicked is one of ['cta_pricing', 'cta_hero', 'cta_offer', 'cta_checkout', 'cta_buy', 'cta_book', 'cta_apply'] and is lead = false
  • leads_no_booking — Leads without booking (person, 30 days; sync: meta, google): People in the last 30 days where is lead = true and is booked = false and is customer = false
  • bookers_no_purchase — Bookers without purchase (person, 60 days; sync: meta, google): People in the last 60 days where is booked = true and is customer = false
  • returning_no_lead — Returning visitors – no lead (visitor, 30 days; sync: meta): Visitors in the last 30 days where session count ≥ 3 and is lead = false
  • checkout_abandoners — Checkout abandoners (visitor, 14 days; sync: meta, google): Visitors in the last 14 days where (clicked is one of ['cta_checkout', 'cta_buy', 'cta_add_to_cart', 'cta_order'] or scroll by path contains '/checkout') and is customer = false
  • webinar_registered_no_show — Webinar registered – no show (person, 30 days; sync: meta, google): People in the last 30 days where is lead = true and is customer = false and not ((scroll by path contains '/live' or custom.webinar_attended is set))

Create them for a funnel with the MCP tool create_default_segments or from the dashboard.

6. What the compiler produces#

compileSegment(rules) returns { sql, params } with $1..$n placeholders; inlineParams(sql, params) renders a self-contained statement for fs_refresh_segment. Example for the rules above:

SELECT sf.visitor_id
FROM session_facts sf
WHERE sf.is_test = false
  AND sf.started_at >= now() - make_interval(days => $1)
  AND sf.visitor_id IS NOT NULL
GROUP BY sf.visitor_id
HAVING (MAX(sf.max_video_pct) >= $2 AND BOOL_OR(COALESCE(sf.is_lead, false)) = $3
  AND (COALESCE(BOOL_OR($4 = ANY(sf.clicked)), false) OR MAX(sf.max_scroll_pct) >= $5))

Only fields in the whitelist (SEGMENT_FIELDS) and custom.<key> are accepted; unknown fields or operators throw before any SQL is built. Values are always parameters, never interpolated.

7. Tips#

  • Build retargeting pools on visitor (works for anonymous traffic through Meta CAPI); build suppression and Customer Match lists on person.
  • Combine behaviour with the absence of an outcome: is_lead = false, is_booked = false, is_customer = false.
  • Use describeRules(rules) (or the dashboard preview) to read a rule set back in English before saving it.
  • Preview counts with preview_segment / POST /segments/preview before enabling sync.

Last updated