Skip to main content

Audiences & lookalikes

An audience turns a slot's entity states into a member list your ad, CRM or warehouse tools consume. Audiences materialize on a schedule, when their segment changes, or on demand (POST …/audiences/{audience_id}:materialize). It is a declarative definition — one of:

KindMembers are…
segmentthe states whose current segment_id is a segment of the slot's segment registry (POST /slots/{slot}/segments:rebuild)
similara frozen lookalike result, intersected with the slot's current states
predicatethe states a predicate over scores, signals and segment_id evaluates true for
{
"name": "winback-lapsing-bingers",
"definition": { "kind": "segment", "segment_id": "sgm_01hv4d0s9t2v5x8z1c4f7j0m3q" },
"schedule": { "kind": "manual" },
"destinations": [
{ "kind": "gcs", "format": "csv" },
{ "kind": "bigquery", "gcp_project": "acme-warehouse", "dataset": "crm", "table": "winback" }
]
}

Definitions are validated against the slot's active bundle when you create or change them, when you preview them, and again when they materialize: a score leaf must name one of the bundle's labels (and a class for multiclass heads), propensity and behavior_surprise leaves need a bundle with behavioral signals enabled, and segment definitions need a current segment registry. Errors name the offending element.

In the console​

Each slot has an Audiences tab. The list shows every audience of the slot with its definition kind (segment, lookalike or rule), its destinations, its refresh mode (on demand) and the status and time of its newest materialization. Opening one shows the stored definition, the destinations, and the materialization history: the version, what triggered it, its status, when it started and finished, member_count with the members that entered and exited, one line per destination delivery, the suppression summary (effective purpose, suppressed and withheld counts) and, for a failed run, its typed error. Materialize now (admins) queues a manual materialization after a confirmation; a run that is already queued or running is refused. Delete asks for the audience name; past materialization records and artifacts are retained under the retention policy. The history refreshes when you press Refresh; nothing polls. Edit (admins) opens the same builder with the stored values and sends only what changed; changes apply from the next materialization and recorded versions stay as they are. Pause and Resume (admins) record status for the scheduler, which is not active yet, so they do not change Materialize now. Editing is for owners and admins. A rule the builder cannot express (for example nested all/any groups or a not) and an Amazon S3 destination are kept exactly as stored, shown but not editable, and a save sends only the fields you actually changed; if nothing differs from the stored audience, Save says so.

Create an audience​

Owners and admins create an audience with New audience on the Audiences tab. The builder has three steps.

  1. Definition. Choose who is in the audience: everyone in one of the slot's segments, the ranked members of a finished lookalike search (frozen when you create the audience), or the entities that match up to eight rules over a model score, the propensity for an event type, behavior surprise, or segment membership, combined with all or any. A live size estimate follows your edits after a short pause: it is an estimate from the slot's current state, up to ten pseudonymous sample ids sit behind a disclosure, and a failed estimate never blocks you (it offers a retry). Whether a rule names a score or event type the slot really has is the estimate's call, not the builder's.
  2. Destination. None, which keeps the member list as the platform artifact, or up to four Cloud Storage prefixes (gs://…, Parquet or CSV) and BigQuery tables. Amazon S3 is not offered because the API refuses it today, and no credential is entered here.
  3. Review. The name starts as an editable suggestion taken from the segment, the lookalike search or the first rule (lowercase letters, digits and hyphens). The review lists what will be created in words, with the exact definition behind a disclosure. The audience refreshes on demand; interval schedules are not accepted by the API yet. If creating fails, nothing was created and your choices are kept.

Predicates​

Predicates are JSON trees of all / any / not nodes over leaves — the same grammar every Tessera trigger uses (https://spec.tessera.dev/schemas/predicate.json). Users likely to order in the next window:

{ "select": { "kind": "propensity", "event_type": "order.completed" }, "op": "gte", "value": 0.6 }

A leaf whose field is absent — no signals block, an unknown propensity key, a null segment_id — never matches (only exists observes absence).

Before saving, POST /slots/{slot}/audiences:preview with {"definition": …} returns estimated_size and up to 10 sample members as entity_hash values. A preview stores nothing, writes nothing and sends nothing.

Materializations​

A materialization runs:

  • on a schedule — "schedule": {"kind": "interval", "every_hours": 24} (1 to 168 hours, optional anchor; the grid is anchor + n × every_hours, anchored at the audience's creation time by default). After an outage only the latest missed run happens, and each scheduled run happens once;
  • when the segment changes — a segment audience is refreshed when a segment.migration touches its slot, at most once per 15 minutes;
  • on demand — POST /slots/{slot}/audiences/{audience_id}:materialize.

A paused audience gets no scheduled or segment-triggered runs. A queued materialization whose job did not start is started again automatically; one still queued after 24 hours fails with the error code launch_lost, so it never blocks the audience's next run; its audience.failed webhook is sent on a best-effort basis (retried for up to another 24 hours). Each successful run is a new version:

  • members.csv and members.parquet under gs://tessera-{env}-exports/projects/{project}/audiences/{audience_id}/{version}/, columns entity_type, entity_id (or entity_hash), audience_id, audience_name, audience_version, materialized_at, sorted by id;
  • a manifest.json with the row count, columns, bundle and sha256 of each file;
  • entered / exited counts against the previous version;
  • one delivery per destination: GCS (the platform artifact, or your gs:// prefix), BigQuery (the table is replaced with the current membership on every run) — the same destinations and grants as exports.

Members pass the platform's suppression and holdout controls before anything is written: the materialization record's suppression block reports the effective purpose and how many members were suppressed or withheld, and files, deliveries, webhooks and counts contain delivered members only.

States written under an older model are excluded and reported as stale_state_rows — run a refresh first for full coverage. GET …/audiences/{audience_id}/materializations is the history; GET …/audiences/{audience_id}/kpis is the size/churn/delivery series.

The audience.synced webhook​

Every finished materialization sends exactly one webhook to your endpoints: audience.synced when every destination succeeded, otherwise audience.failed with a typed error.

{
"id": "whe_01hv5000000000000000audsyn",
"type": "audience.synced",
"created_at": "2026-01-01T00:00:00Z",
"project_id": "proj_01hv4d0s9t2v5x8z1c4f7j0m3q",
"data": {
"audience_id": "aud_01hv4d0s9t2v5x8z1c4f7j0m3q",
"audience_name": "winback-lapsing-bingers",
"slot_id": "slot_01hv4d0s9t2v5x8z1c4f7j0m3q",
"environment": "live",
"version": 3,
"materialization_id": "amv_01hv4d0s9t2v5x8z1c4f7j0m3q",
"member_count": 12004,
"entered": 311,
"exited": 97,
"pseudonymized": false,
"artifact": {
"format": "csv",
"url": "https://storage.googleapis.com/…/members.csv?X-Goog-Signature=…",
"expires_at": "2026-01-08T00:00:00Z",
"sha256": "…",
"rows": 12004
},
"destinations": [{ "kind": "gcs", "status": "succeeded", "rows": 12004, "error": null }],
"materialized_at": "2026-01-01T00:00:00Z"
}
}

A receiver that syncs an ad or CRM audience needs no Tessera adapter:

  1. verify Tessera-Signature exactly as described in Verifying signatures (HMAC over the raw body, 300-second tolerance) and deduplicate on id;
  2. download data.artifact.url (valid for 7 days — longer than the retry window, so a retried delivery can still fetch it) and check data.artifact.sha256;
  3. replace the audience in the downstream tool with the file's rows.

Segment audiences already refresh themselves on segment.migration. To react in your own systems as well, subscribe to segment.migration and call POST /slots/{slot}/audiences/{audience_id}:materialize when data.to_segment_id or data.from_segment_id is the audience's segment — for example, a viewer moving into "lapsing-bingers" refreshes the win-back audience that feeds your CRM.

Tessera keeps the newest 30 versions of each audience (older records and files are removed daily) and lookalike files for 90 days.

Lookalikes​

POST /slots/{slot}/similar ranks the slot's states by how close their stored model embedding is to your seeds:

{
"seeds": [
{ "type": "player", "id": "PL-901" },
{ "type": "player", "id": "PL-944" }
],
"k": 10000
}

The job averages the normalized seed embeddings, scores every other state of the active bundle by cosine similarity (ties broken by entity id) and keeps the top k (1–100,000, from 1–100 seeds; optional filters is a predicate). Poll GET /slots/{slot}/similar/{similar_id}; once succeeded it carries 15-minute signed URLs for the ranked members.parquet and its CSV twin (rank, entity_type, entity_id, similarity). Seeds without a current state fail the job with seed_state_missing; more than one million candidates fail with entity_count_exceeded. Create a kind: "similar" audience to deliver a lookalike result to your destinations — its member list is frozen at creation and never re-ranked.

Pseudonymized audiences​

PUT /audience-settings with {"pseudonymize_audiences": true} makes every subsequent materialization and lookalike carry the per-project entity_hash instead of entity_id — files, destination tables and webhooks — so raw ids never leave Tessera; join back inside your own warehouse. The first version after a flip reports diff_reset: true. If the service cannot read the per-project key, a pseudonymized materialization or lookalike fails with unavailable (reason entity_key_unavailable) and writes nothing, and a preview answers 503 with the same reason.

S3 destinations​

Every destination kind (gcs, bigquery, s3) is available. An s3 destination names a vaulted credential of the project (credential_id, as for exports); the credentials are used inside the materialization only and never appear in records, logs or webhooks.