> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Three ways to materialize an audience at send time

> LLM-suggested segments, click-cohort retargeting, and on-the-fly CSV export are the three paths that turn an audience idea into something a campaign can act on. When to use each, how the click cohort is attributed, and what audit trail covers the CSV egress.

# 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](/guides/cdp-segments) 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

|                 | LLM-suggested segment                                                                                                | Click-cohort resolution                                                                                 | On-the-fly CSV export                                                  |
| --------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| **Durability**  | Nothing persisted — a suggestion pre-fills the create dialog; the segment exists only after you confirm              | Persists a static segment snapshot (`auto_refresh = false`); generate a fresh cohort for a later window | Nothing persisted — the audience is resolved live and leaves as a file |
| **Editability** | Fully editable before creation — adjust the suggested filters in the dialog                                          | Not editable after materialization (static snapshot); re-create with a different scope or window        | Fully editable per request — change the filter AST, re-export          |
| **Retention**   | The segment you optionally create refreshes per the [segment recompute model](/concepts/cdp-segment-recompute-model) | Membership frozen at creation; the cohort never drains                                                  | None — pure egress; audience state lives only in the downloaded file   |

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](/concepts/consent-and-suppression-model).

## Section 6 — See also

* [CDP identity resolution](/concepts/cdp-identity-resolution) — how a
  tracked event becomes a contact the cohort resolver can attribute.
* [CDP segment recompute model](/concepts/cdp-segment-recompute-model) —
  the refresh lifecycle of the segments these flows optionally create.
* [CDP clean-room model](/concepts/cdp-clean-room-model) — the governed
  path for sharing audience data without raw egress.
* [CDP segments guide](/guides/cdp-segments) — the how-to for building and
  saving the segments this page materializes.
* [Consent and suppression model](/concepts/consent-and-suppression-model)
  — the privacy governance layer above all three paths.
