Skip to main content

Three ways to materialize an audience at send time

Building an audience is one thing; getting it into a sendable shape is another. Orbit ships three materialization paths that sit between “I have an audience in mind” and “the campaign has a target,” and they solve different problems:
  1. LLM-suggested segment — you don’t yet know which audiences are worth building. The suggester reads your recent CDP signal and proposes high-value segments as validated filter definitions.
  2. Click-cohort retarget — you know exactly whom you want: the contacts who clicked a specific link in a prior campaign. The cohort resolver turns that click set into a static, retargetable segment.
  3. On-the-fly CSV export — you have a filter definition but don’t want to persist it as a segment. The export resolves the audience live and hands you the matched contacts as a CSV download.
The sections below define each path, when it is the right one, and the decision table at the end routes the choice between them.

Section 1 — LLM-suggested segment

POST /api/v1/segments/auto-suggest proposes segments you have not created yet. It takes no audience description — instead it reads the org’s last-30-day CDP event volume, the top event names, the top contact tags, and the predictive segment-label histogram, and asks the LLM to propose up to five high-value segment definitions given that signal. An optional focus_hint (up to 500 characters, e.g. “focus on retention”) steers the suggestions. Every suggestion is returned as four parts: a name, a one-sentence description framed at the marketer, a rationale explaining why the segment is high value for this org’s event mix, and a filters filter AST. Before a suggestion reaches you it is dry-run against the segmentation engine on your tenant’s data: suggestions that reference an unknown field, use a mismatched operator, or blow up in the engine are dropped, and the survivors arrive with a preview_match_count. The response is therefore “trust everything we returned” — the dashboard’s empty-state on the segments page calls this endpoint to render “Try one of these” cards. Suggestions are not persisted. Clicking one pre-fills the create-segment dialog; the segment row exists only after you confirm the creation. When the org has no useful signal (zero contacts, or zero events in the last 30 days — including a brand-new tenant whose CDP tables have not finished provisioning), the endpoint returns an empty suggestion list with a note instead of an error.

How this differs from the autopilot endpoint

There are two LLM segment endpoints and they are easy to confuse:
  • POST /api/v1/contacts/segments/autopilot takes a user-supplied natural-language description (“everyone who clicked the pricing page but didn’t book a demo”) and translates it into a segment definition plus a match-count preview on one round-trip. You bring the idea; the LLM compiles it.
  • POST /api/v1/segments/auto-suggest takes no description at all. The org’s own event mix supplies the raw material; the LLM supplies the ideas. Use it for discovery — the “what should I even build?” question — and use autopilot when you already know the audience you want.
Both treat LLM output as untrusted input: filter fields are constrained to the segmentation engine’s allowlist, operators to a fixed enum, and each suggested definition is engine-validated before it is shown.

Section 2 — Click-cohort resolution

POST /api/v1/segments/from-clicks closes the click→contact loop. Orbit already shortens links, records clicks, and reports aggregate click counts — but “how many clicked” never answered “which contacts clicked.” This endpoint resolves that. Scope the cohort one of two ways:
  • link_id — every recipient who clicked that one tracked short link.
  • campaign_id — recipients who clicked any tracked link in the campaign.
  • window_days (optional, 1–365) — only count clicks within the last N days, for recency-scoped re-engagement. Omit it to consider all-time clicks.
The resolver walks the existing attribution chain — click event → short link → the message that carried the link → the contact the message was sent to — and collects the distinct recipients. Attribution depends on the link having been sent in a message bound to a contact. A click on a link pasted into a browser or shared on social media has no recipient to attribute, and is excluded by construction. If nothing attributable is found, the endpoint returns 422 EMPTY_COHORT and explains that no cohort exists to materialize. The result is a static contact segment (no rules, auto_refresh = false) materialized through the same path any other segment uses — so campaigns, exports, and audience activation all key on its segment_id with no special handling. Click engagement is an event, not a live trait: the cohort is a point-in-time snapshot that stays stable for the re-engagement blast instead of draining on the next refresh tick. To capture a later window, generate a fresh cohort. Soft-deleted contacts are excluded, membership is hard-capped so a click-heavy tenant cannot materialize an unbounded audience, and the creation is audit-logged with the scope (link or campaign), the window, and the member count. Naming is deterministic when you don’t supply one — Clicked link <id> (last 30d) — so the same scope always yields the same label.

Section 3 — On-the-fly CSV export

POST /api/v1/segments/export.csv exports an unsaved audience. It takes the same filter AST the segment builder and the auto-suggest endpoint speak, resolves it live against your contacts, and returns the matched rows as an RFC-4180 CSV attachment — no segment row, no membership materialization, no waiting on a refresh tick. (The saved-segment equivalent, for audiences you did persist, is GET /api/v1/contacts/segments/{id}/export.csv; the CDP segments guide covers the saved flow.) The request body is three fields: filters (the audience definition), an optional limit below the hard cap for a quick sample download, and an optional filename stem — sanitized so it can never break the download headers. The response is the CSV plus, when the audience exceeded the cap, an X-Export-Truncated: true header. Guardrails on this path:
  • Hard row cap of 50,000. The resolve is synchronous — the reply is one CSV string — so the cap also bounds worker memory. An “all contacts” filter can never resolve more than the ceiling; when the result is truncated, the header tells you and the truncation is audited. For larger audiences, persist the segment and use the streaming saved-export path.
  • Role and scope gate. Exporting an audience is a bulk PII pull, so it requires the owner, admin, or developer role and the contacts:write scope — a viewer or a contacts:read key cannot pull full contact rows.
  • PII visibility. Every row passes through the same masking rules the dashboard applies: phone and email honour the caller’s role and reveal context exactly as the contact list renders them for that caller. An export never shows more than the dashboard would.
  • Rate limit. 5 exports per minute per tenant.
The exported columns mirror the saved-segment export: contact_id, first_name, last_name, email, phone, country_code, lifecycle_stage, tags, created_at.

Section 4 — Contrast and decision

Route the choice by what you already know:
  • Don’t know which audiences are worth building? Start with auto-suggest and let your own event mix propose them.
  • Know the audience in words? Use the autopilot endpoint (/contacts/segments/autopilot) to compile the description.
  • Know the audience as “everyone who clicked that link”? Build a click cohort — attribution is resolved for you.
  • Need the audience outside Orbit, once, without saving anything? Export the CSV.

Section 5 — Egress gating and the audit trail

The CSV path is the only one of the three where contact data leaves the product, so it carries the strictest posture. The gate has three layers:
  1. Actor gate. Owner, admin, or developer role with the contacts:write scope. Lower-privileged actors are refused before any contact row is read.
  2. Content gate. PII visibility is resolved per request and applied to every row, so the file can never contain more phone/email detail than the caller could see in the dashboard.
  3. Volume gate. The 50,000-row hard cap and the 5-per-minute rate limit bound how much a single actor can pull.
Every export — including truncated ones — writes an audit log entry on the org’s audit trail recording the exporting user, the row count, the cap, and whether truncation occurred. The click-cohort and auto-suggest paths audit their own events in the same trail (cohort creation with scope and member count; suggestion generation with dropped counts), so a compliance reviewer can reconstruct which audiences were materialized and which left the product. All three are tenant-owned controls: they govern egress from your own tenant’s data. The broader privacy posture — suppression, erasure — is defined in the consent and suppression model.

Section 6 — See also