---
title: "Preview a campaign audience"
method: POST
path: "/v1/loyalty/campaigns/audience-preview"
tags: ["Loyalty"]
---

# Preview a campaign audience

`POST /v1/loyalty/campaigns/audience-preview`

## Request

- Base URL: `https://api.prod.keyban.io`
- URL: `https://api.prod.keyban.io/v1/loyalty/campaigns/audience-preview`
- Auth: API key in header `X-Api-Key`

Estimate the reach of a targeting filter before a campaign is created or sent. Returns how many opted-in customers match the filter, how many are reachable right now, and how many are temporarily withheld by the per-card notification cap (with the time the whole audience frees up). This is a wizard estimate: the authoritative per-recipient consent and cap checks run again at send time. A POST (not GET) because the targeting filter is a structured body, but it has no side effects.

## Request body

- AudiencePreviewRequestDto
  - `applicationId` string, uuid, required: Application (brand) whose audience is previewed. The preview is scoped to this application only, matching the application a campaign is created against.
  - `targeting` object, nullable: Targeting filter to preview. Null or omitted previews the whole opted-in audience of the application.
    - `minPoints` integer: Inclusive lower bound on the loyalty point balance, in points. Omit for no lower bound.
    - `maxPoints` integer: Inclusive upper bound on the loyalty point balance, in points. Omit for no upper bound.
    - `minVisits` integer: Minimum number of distinct visit days over the application visit window (LoyaltySettings.visitsWindowDays). Omit to ignore visit frequency.
    - `maxVisits` integer: Inclusive upper bound on distinct visit days over the application visit window (LoyaltySettings.visitsWindowDays). Set to 0 to target customers with no visit in the window (inactive/win-back). Omit for no upper bound.
    - `rewardTierIds` string[]: Restrict to customers whose effective reward tier is one of these tier IDs. Omit to target every tier.
  - `channels` string[]: Wallet channels the campaign targets. A matching customer is reachable only if they have an installed card on at least one of these channels — the same per-channel card check the send pipeline applies (NoCardOnChannel). Omitted means "any channel": a card on either Apple or Google counts.

## Response `200`

Audience preview computed successfully

- AudiencePreviewDto
  - `matchingCount` integer, required: Opted-in customers matching the targeting filter — the campaign reach before the frequency cap and the card-installation check are applied.
  - `accessibleCount` integer, required: Subset of matching customers reachable right now: they have an installed wallet card on at least one targeted channel and are below the all-channel notification cap over the rolling window. Invariant: accessibleCount + cappedCount + noCardCount === matchingCount.
  - `cappedCount` integer, required: Subset of matching customers that have an installed card but are temporarily withheld because they already hit the notification cap. They become reachable again at fullAvailabilityAt.
  - `noCardCount` integer, required: Subset of matching customers with no installed wallet card on any targeted channel (Apple pass never registered on a device, or Google card never added). They match the targeting and consented, but the send pipeline excludes them as NoCardOnChannel, so they are not reachable.
  - `fullAvailabilityAt` string, date-time, nullable, required: Earliest RFC 3339 instant at which every capped customer can be reached again. Null when cappedCount is 0 (no customer is capped). Serialized from a Date to an ISO 8601 string by the HTTP layer.

## Other responses

- `400`: Invalid targeting filter

---

[API](https://skmtc.dev/keyban/apis/keyban-rest-apis.md) · [All operations](https://skmtc.dev/keyban/apis/keyban-rest-apis/llms.txt) · [OpenAPI document](https://skmtc.dev/keyban/apis/keyban-rest-apis/revisions/0bb79a1da961?raw)
