Guides
Audience Preview & Sizing

Audience Preview & Sizing

Validate an audience segment and sample matching records before activating it in a campaign. The Audiences API exposes a preview operation that runs the audience rule against live profile data and returns an exact match count plus a sample of records — no campaign send required.

When to use the preview API:

  • Sanity-check a new segment before marketing sends to it
  • Size campaigns — get an up-to-the-second reach number from your backend or campaign tool
  • Verify a specific user is (or isn't) in a cohort during support triage
  • Guard against schema drift in CI — alert when a segment's count changes drastically between deploys

Prerequisites

  • An audience definition exists in the Experiture console (created by a marketing or admin user)
  • A bearer token with cdp:audiences:preview scope — see Authentication

Audience definitions are created in the console, not via the public API. This guide covers reading and exercising existing audiences programmatically.


Running a preview

curl -X POST https://api.experiture.ai/public/v1/audiences/aud_01HXYZ/preview \
  -H "Authorization: Bearer <your_access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "limit": 100 }'

Response:

{
  "success": true,
  "data": {
    "audienceId": "aud_01HXYZ",
    "matchCount": 18432,
    "isExact": true,
    "executionTimeMs": 412,
    "fieldsUsed": ["email", "tier", "last_order_at"],
    "sampleRecords": [
      {
        "email": "a@example.com",
        "tier": "gold",
        "last_order_at": "2026-04-20T12:00:00Z"
      }
    ]
  }
}

matchCount is the authoritative reach. sampleRecords contains up to limit rows projected to only the fields referenced by the audience rule. fieldsUsed shows which profile fields drive the audience — useful for debugging why a user does or doesn't qualify.


The limit parameter

limit controls how many sample records come back. It does not affect matchCount — the count is always computed over the full dataset. Valid range: 1–1000. Default: 100.

limitUse case
1Count only — fastest, minimal payload
10–100Spot-check member quality
1000Debugging, searching for a specific user

For most programmatic checks, use limit: 1 — it returns the count plus one sample row to confirm the audience is non-empty. The sample is not random — it's the first rows the query engine encountered, so don't use it for statistical analysis.


Sizing before campaign activation

Check that an audience is within expected bounds before allowing a campaign to dispatch:

import os, requests
 
API_KEY = os.environ["EXPERITURE_API_KEY"]
BASE = "https://api.experiture.ai/public/v1"
 
def preview_audience(audience_id: str, limit: int = 1) -> dict:
    resp = requests.post(
        f"{BASE}/audiences/{audience_id}/preview",
        headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
        json={"limit": limit},
    )
    resp.raise_for_status()
    return resp.json()["data"]
 
def validate_audience_size(
    audience_id: str,
    min_size: int = 1000,
    max_size: int = 5_000_000,
) -> int:
    preview = preview_audience(audience_id, limit=1)
    match_count = preview["matchCount"]
 
    if match_count < min_size:
        raise ValueError(
            f"Audience {audience_id} is too small: "
            f"{match_count} < {min_size}"
        )
    if match_count > max_size:
        raise ValueError(
            f"Audience {audience_id} is suspiciously large: "
            f"{match_count} > {max_size} — possible rule regression"
        )
 
    return match_count
 
size = validate_audience_size("aud_01HXYZ")
print(f"Audience OK: {size:,} profiles")
activate_campaign("spring_promo_2026", audience_id="aud_01HXYZ")

limit=1 returns the count plus a single sample record — the minimum payload that confirms the audience is non-empty.


Debugging: "why is this user not in the audience?"

Get the audience's fieldsUsed and check the user's profile directly:

# 1. Get a preview to see which fields drive the audience
preview = preview_audience("aud_01HXYZ", limit=5)
print("Fields used:", preview["fieldsUsed"])
 
# 2. Check whether the target user appears in a large sample
preview_large = preview_audience("aud_01HXYZ", limit=1000)
target_email = "jane@example.com"
 
matches = [r for r in preview_large["sampleRecords"] if r.get("email") == target_email]
 
if matches:
    print(f"{target_email} IS in the sample. Profile data:")
    print(matches[0])
else:
    print(
        f"{target_email} not found in sample of {len(preview_large['sampleRecords'])} records. "
        f"Check profile directly — look at: {preview['fieldsUsed']}"
    )

For definitive answers, look up the profile in the Experiture console under Profiles → Search and compare the values of fieldsUsed against the audience's filter criteria.


Drift detection in CI

Snapshot counts for critical audiences as part of every deployment. A sudden change in count almost always indicates a schema change or a rule regression.

import os, sys
 
AUDIENCE_BOUNDS = {
    "aud_gold_customers":   {"min": 10_000,   "max": 50_000},
    "aud_active_subscribers": {"min": 500_000,  "max": 2_000_000},
    "aud_lapsed_users":     {"min": 20_000,   "max": 200_000},
}
 
failures = []
 
for audience_id, bounds in AUDIENCE_BOUNDS.items():
    preview = preview_audience(audience_id, limit=1)
    count = preview["matchCount"]
    lo, hi = bounds["min"], bounds["max"]
 
    if not (lo <= count <= hi):
        failures.append(
            f"{audience_id}: {count:,} is outside expected [{lo:,}, {hi:,}]"
        )
    else:
        print(f"  OK {audience_id}: {count:,}")
 
if failures:
    print("\nAudience drift detected:")
    for msg in failures:
        print(f"  FAIL {msg}")
    sys.exit(1)

Wire this script into your CI pipeline as a post-deploy smoke test.


Rate limits

Preview rate limits depend on your plan — see rate limits documentation for current values. Preview runs against the live warehouse. Typical latency: 200 ms – 2 s. Hard timeout: 30 s. Very broad audiences with complex rules may time out — the API returns 504 in that case.

If you're driving preview from a UI (e.g. a live segment size estimator), add client-side debouncing to avoid burning the rate limit on every keystroke.


Understanding isExact

In almost all cases, isExact: true — the count is precise. For extremely broad audiences (> 100M candidates across complex join conditions), the count is approximate and isExact: false. The sample records are still real rows, but the count should be treated as an order-of-magnitude estimate.

If you need an exact count for a very large audience, contact support — they can run a non-interactive count query on your behalf.


Common failure modes

404 CDP_ETL.NOT_FOUND — the audience ID doesn't exist or belongs to a different workspace. Verify the ID in the Experiture console under Audiences.

403 forbidden — your token is missing the cdp:audiences:preview scope. Add the scope in My Organization → API Access.

504 timeout — the audience rule is too complex or targets too many records for the 30-second query window. Simplify the rule or contact support.

matchCount: 0 unexpectedly — the audience rule may reference fields that haven't been written yet, or the rule itself has an error. Check fieldsUsed in the response and verify those fields exist on recent profiles via Profiles → Search.


See Also