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:
| Kind | Members are… |
|---|---|
segment | the states whose current segment_id is a segment of the slot's segment registry (POST /slots/{slot}/segments:rebuild) |
similar | a frozen lookalike result, intersected with the slot's current states |
predicate | the 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.
- 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.
- 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. - 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, optionalanchor; the grid isanchor + 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
segmentaudience is refreshed when asegment.migrationtouches 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.csvandmembers.parquetundergs://tessera-{env}-exports/projects/{project}/audiences/{audience_id}/{version}/, columnsentity_type,entity_id(orentity_hash),audience_id,audience_name,audience_version,materialized_at, sorted by id;- a
manifest.jsonwith the row count, columns, bundle and sha256 of each file; entered/exitedcounts 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:
- verify
Tessera-Signatureexactly as described in Verifying signatures (HMAC over the raw body, 300-second tolerance) and deduplicate onid; - download
data.artifact.url(valid for 7 days — longer than the retry window, so a retried delivery can still fetch it) and checkdata.artifact.sha256; - 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.