Audience filters (AST v1)

One JSON format targets everything: saved segments, inline sends, previews, and the dashboard's visual builder. It's versioned ("v": 1) — a stable contract you can generate, store, and diff.

{ "and": [
    { "attr": "country", "op": "in", "value": ["NP", "IN", "BD"] },
    { "or": [
        { "attr": "properties.plan", "op": "eq", "value": "pro" },
        { "attr": "properties.lifetime_value", "op": "gt", "value": 100 } ] },
    { "attr": "last_seen_at", "op": "within_last", "value": "7d" },
    { "channel": "webpush", "op": "reachable" }
] }

Attributes

Reserved (exist for every user, several auto-captured): first_name last_name email phone gender country region city language timezone first_seen_at last_seen_at external_id. Country is auto-set from IP at registration (ISO 3166-1 alpha-2); language/timezone come from the SDK.

Custom: anything you send in properties, referenced as properties.<key>. Keys auto-register in the property catalog with inferred types — only cataloged attributes are filterable; unknown ones are rejected, never guessed.

Operators

TypeOperators
alleq neq in not_in exists not_exists
stringcontains starts_with
numbergt gte lt lte between (between takes [min, max])
datebefore after within_last not_within_last anniversary + comparisons. Windows: "24h" "7d" "2w" "3m". anniversary matches month+day — birthday campaigns.
arrayincludes includes_any includes_all
reachability{"channel": "webpush"|"apns"|"fcm", "op": "reachable"|"unreachable"}

Combinators & limits

{"and":[…]}, {"or":[…]}, {"not": …} — nest up to 8 levels deep, 64 clauses total. Values are always bound as SQL parameters — filter input can never inject.

Preview before sending

POST /v1/segments/preview   { "filter": … }   →   { "count": 1207 }

The dashboard shows this live on every edit; use it in CI to sanity-check generated filters.

Or skip the JSON entirely

In the dashboard: “pro users in Nepal active this week” → ✨ AI build → the generated filter is validated against your catalog, compiled by the real engine, and shown with its live count for you to confirm. How that stays trustworthy →