# Revenuecat Audiences

> Use before sharing a link to a filtered customer list or audience, and when identifying, filterin…

Use before sharing a link to a filtered customer list or audience, and when identifying, filtering, or ranking the user's customers (a segment, who they are, a product or duration, spend, renewals, status, country, attribution).

- Page: https://ss33.ai/skills/revenuecat/revenuecat-audiences-83251504244
- Publisher: revenuecat
- Category: [Data & Analytics](https://ss33.ai/skills/category/data-and-analytics-m1r3i9) › [Analysis & BI](https://ss33.ai/skills/category/data-and-analytics-m1r3i9/analysis-and-bi-b2nit8)
- License: MIT
- Source: https://github.com/revenuecat/ai-toolkit/blob/ccfc038185ec457b2ac9077f3bbf1ba947a44dbb/revenuecat/skills/revenuecat-audiences/SKILL.md

## Skill

SKILL.md: https://ss33.ai/api/v1/ss33/download/revenuecat/revenuecat-audiences-83251504244

# Audience filters and dashboard links

Use Audiences to filter the user's customers and to share a dashboard link to that set. Filters
select the segment; `sort_by` / `sort_direction` on `preview-audience` order the returned sample.

There is no first-class CLI command for audiences. Do not tell the user RevenueCat cannot filter
or segment customers.

## Identifying, filtering, or ranking customers

Audiences filter on the [fields](#fields) below, including:

- Product and duration — `latestProduct`, `allPurchasedProductIds`, `latestPurchasedOffering`,
  entitlements, offers
- Spend and renewals — `totalSpent` (filter, and `sort_by: total_spent`), `totalRenewals` (filter
  only; there is no renewal sort)
- Subscription state — `status`, trial, auto-renew intent, ownership
- Store, platform, and country — `platform`, `latestStore`, `country`, `storefront`
- Dates — first seen, purchases, renewals, expiration, trial, cancellation
- Attribution and experiments — media source, campaign, ad group, keywords, price experiment
- Identity and custom attributes — app user ID, email, locale, `customAttribute:{key}`

Map the question onto those fields, filter, then read a sample.

1. Fetch project-specific values with `get-audience-filter-options` for any field marked
   project-specific that the question uses (`project_id`, `fields`).
2. Call `preview-audience` with `project_id` and `body.rules` in the [filter rule](#the-filter-rule)
   shape (for a saved audience from `list-audiences`, pass `body.audience_uuid` instead). It is
   read-only and saves nothing. Do not `create-audience` just to inspect a segment.
3. To rank or name a superlative, pass `sort_by` (`status`, `latest_auto_renew_intent`,
   `first_seen_at`, `last_seen_at`, `total_spent`) and `sort_direction` (`asc` default, or `desc`)
   as top-level parameters, not inside `body`. `sort_by: total_spent` with `sort_direction: desc`
   returns the highest spenders first, so the first `customer_sample` row is the top spender. The
   sample is capped (typically 50): it is the top or bottom matches, not every customer.
4. Answer "how many" from `stats` (`total_customers`, `active_subscriptions`, `active_trials`,
   `total_revenue` in `currency`), never from the sample size. When `stats.is_approximate` is true,
   give counts and revenue as approximate ("about 6,400 customers").
5. Name customers only from fields `customer_sample` returned (`app_user_id`, `email`, `status`,
   `total_spent`, `latest_product_name`, first/last seen). Without `sort_by` the sample is
   unordered: say it is a sample of matches, not a ranking.
6. `totalRenewals` is a filter only, not a `sort_by` value or a sample column. Filter on a high
   threshold and report the matches; do not invent a renewal count or claim a unique maximum.
7. Share the dashboard link. The `preview-audience` result for `body.rules` includes a line
   starting "Dashboard URL for this result"; link that exact URL. Build one yourself
   ([constructing a link](#constructing-a-link)) only when no tool result gave you a URL.

## Constructing a link

Use this only when no tool result gave you a "Dashboard URL for this result" line. Two shapes: a
filtered `all-customers` link for ad-hoc exploration, and a saved-audience link after
`create-audience`.

1. Get the project ID from `list-projects`. For dashboard URLs, **strip the `proj` prefix**.
2. Pick fields and operators from the [field tables](#fields). Do not invent field or operator
   names.
3. For project-specific fields, fetch valid values with `get-audience-filter-options` first.
4. Assemble the [rule JSON](#the-filter-rule): one group per OR-branch, conditions inside a group
   for AND, values encoded per [value formats](#value-formats).
5. Serialize and URL-encode the rule with a short script (see [encoding the rule](#encoding-the-rule))
   — do not encode by hand.
6. Append it as the `filters` query param on
   `https://app.revenuecat.com/projects/{project_id}/customer-lists/all-customers`.

## URL format

```
https://app.revenuecat.com/projects/{project_id}/customer-lists/all-customers?filters={encoded_rule}
```

- `{project_id}` — short hex ID from `list-projects` with `proj` stripped.
- Filters only work on the `all-customers` list. The unfiltered Audiences home is
  `/projects/{project_id}/customer-lists`.

## Linking to a saved audience

The `create-audience` and `get-audience` results include a "Dashboard URL for this result" line;
link that exact URL. If you only have `list-audiences` output, link with `customer_list_id`, the
field returned alongside `id`:

```
https://app.revenuecat.com/projects/{project_id}/customer-lists/{customer_list_id}
```

**An audience's `id` (`aud…`) and its `customer_list_id` (`list…`) are different identifiers.**
The dashboard route only resolves `customer_list_id`; an `aud…` id in that slot renders
"Audience not found".

**Correct:**

```
https://app.revenuecat.com/projects/56965ae1/customer-lists/list7c1f0a2b93
```

**Wrong** (the audience `id` instead of the `customer_list_id`):

```
https://app.revenuecat.com/projects/56965ae1/customer-lists/audf0269cdf3df84dd2
```

Do not add a `filters` param to a saved-audience link — the audience carries its own rules. If
the response has no `customer_list_id`, link to `/customer-lists` and name the audience rather
than guessing an id.

## Offering to save the filtered view

A filtered `all-customers` link is ad-hoc — nothing about it is saved. Say so in one short
sentence when you share one, and offer to save it: "This view isn't saved — want me to save it
as an audience so you can find it later?"

Offer once per conversation. If they accept, call `create-audience` with the same rule you built
for the link, then link to it. Do not offer when the link is already to a saved audience.

## The filter rule

The `filters` value is URL-encoded JSON with this shape:

```json
{
  "groups": [
    {
      "conditions": [{ "field": "platform", "operator": "is", "value": "android" }]
    }
  ]
}
```

- Conditions within a group combine with AND.
- Groups combine with OR.
- `value` is always a JSON string — booleans as `"true"`/`"false"`, numbers as `"42"`, lists as a
  comma-separated string, date ranges and relative dates as stringified JSON (see
  [value formats](#value-formats)).

**Correct** (compact JSON, whole value URL-encoded):

```
?filters=%7B%22groups%22%3A%5B%7B%22conditions%22%3A%5B%7B%22field%22%3A%22platform%22%2C%22operator%22%3A%22is%22%2C%22value%22%3A%22android%22%7D%5D%7D%5D%7D
```

**Wrong** (raw JSON, spaces, unencoded quotes/braces):

```
?filters={"groups": [{"conditions": [...]}]}
```

## Encoding the rule

```python
import json
from urllib.parse import quote

rule = {
    "groups": [
        {"conditions": [{"field": "platform", "operator": "is", "value": "android"}]},
    ]
}

print(quote(json.dumps(rule, separators=(",", ":")), safe=""))
```

## Fields

The Audiences preview table shows only **Customer**, **Subscription Status**,
**Auto-Renewal Status**, **Spent**, and **Latest Purchase**. Other attributes appear in the CSV
from **Export all**, or on a customer's profile.

Use these exact `field` strings. See
[Audiences](https://www.revenuecat.com/docs/dashboard-and-metrics/audiences).

### Text fields

Operators: `is`, `isNot`, `contains`, `doesNotContain`, `isEmpty`, `isNotEmpty`.

| Field                     | Meaning                           |
| ------------------------- | --------------------------------- |
| `customerId`              | App user ID                       |
| `originalAppUserId`       | Original app user ID              |
| `email`                   | Email                             |
| `phoneNumber`             | Phone number                      |
| `locale`                  | Locale                            |
| `appVersion`              | App version                       |
| `sdkVersion`              | SDK version                       |
| `platformVersion`         | Platform (OS) version             |
| `projectId`               | Project ID (e.g. `proj1ab2c3d4`)  |
| `projectName`             | Project name                      |
| `appConfigId`             | App ID (e.g. `app1ab2c3d4`)       |
| `appConfigName`           | App name                          |
| `idfa`                    | IDFA                              |
| `idfv`                    | IDFV                              |
| `gpsAdId`                 | GPS ad ID                         |
| `latestPurchasedOffering` | Latest purchased offering         |
| `latestOffer`             | Latest offer identifier           |
| `latestEntitlements`      | Latest entitlement identifiers    |
| `allPurchasedProductIds`  | All purchased product identifiers |

### Enum fields

Operators: `is`, `isNot`, `isAnyOf`, `isNotAnyOf`, `isEmpty`, `isNotEmpty`.

| Field                    | Values                                                                                                                    |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `platform`               | `iOS`, `android`, `web`, `macOS`, `amazon`, `roku`, `tvOS`, `visionOS`, `watchOS`                                         |
| `status`                 | `active`, `trialing`, `in_grace_period`, `in_billing_retry`, `paused`, `expired`, `incomplete`, `unknown`                 |
| `latestStore`            | `app_store`, `play_store`, `promotional`, `mac_app_store`, `stripe`, `amazon`, `roku`, `rc_billing`, `paddle`, `external` |
| `anyActiveStore`         | Any active store — same store identifiers as `latestStore`                                                                |
| `latestOwnershipType`    | `PURCHASED`, `FAMILY_SHARED`                                                                                              |
| `latestOfferType`        | `no_offer`, `free_trial`, `introductory_offer`, `offer_code`, `promotional_offer`, `win_back_offer`, `unspecified_offer`  |
| `priceExperimentVariant` | `a`, `b`, `c`, `d`                                                                                                        |
| `country`                | Last seen country — ISO 3166-1 alpha-2 codes (e.g. `US`, `DE`)                                                            |
| `latestStoreCountry`     | ISO 3166-1 alpha-2 country codes                                                                                          |
| `storefront`             | Store country — ISO 3166-1 alpha-2 codes                                                                                  |
| `mediaSource`            | project-specific — [fetch valid values](#fetching-project-specific-values)                                                |
| `campaign`               | project-specific                                                                                                          |
| `adGroup`                | project-specific                                                                                                          |
| `ad`                     | project-specific                                                                                                          |
| `keyword`                | project-specific                                                                                                          |
| `creative`               | project-specific                                                                                                          |
| `priceExperimentId`      | project-specific                                                                                                          |
| `latestProduct`          | product IDs of the project — fetch valid values                                                                           |

### Boolean fields

Operators: `is`, `isNot`. Value is exactly `"true"` or `"false"`.

| Field                            | Meaning                                              |
| -------------------------------- | ---------------------------------------------------- |
| `hasMadeSandboxPurchase`         | Has made a sandbox purchase                          |
| `hasMadeNonSubscriptionPurchase` | Has made a non-subscription purchase                 |
| `latestAutoRenewIntent`          | Auto-renewal status (`true` = set to renew)          |
| `isCurrentlyTrialing`            | Currently trialing                                   |
| `isRcPromo`                      | Has been granted an entitlement via RC (promotional) |

### Number fields

Operators: `equal`, `notEqual`, `greaterThan`, `greaterThanOrEqual`, `lessThan`,
`lessThanOrEqual`, `isEmpty`, `isNotEmpty`. Value is a numeric string, e.g. `"50"`.

| Field           | Meaning                  |
| --------------- | ------------------------ |
| `totalSpent`    | Total spent              |
| `totalRenewals` | Total number of renewals |

### Date fields

Operators: `before`, `beforeOrOn`, `on`, `after`, `afterOrOn`, `within`, `between`, `notBetween`,
`isEmpty`, `isNotEmpty`.

| Field                  | Meaning                        |
| ---------------------- | ------------------------------ |
| `firstSeenAt`          | First seen                     |
| `lastSeenAt`           | Last seen                      |
| `firstPurchaseAt`      | First purchase                 |
| `mostRecentPurchaseAt` | Most recent purchase           |
| `mostRecentRenewalAt`  | Most recent renewal            |
| `latestExpirationAt`   | Latest expiration              |
| `trialStartAt`         | Trial start                    |
| `trialEndAt`           | Trial end                      |
| `subscriptionOptOutAt` | Most recent cancellation       |
| `trialOptOutAt`        | Most recent trial cancellation |

### Custom attribute fields

Filter with `customAttribute:{key}` (e.g. `customAttribute:favorite_team`). They use the enum
operators. Fetch known keys and values with `get-audience-filter-options` — never invent a key.

## Value formats

- `isEmpty` / `isNotEmpty` — set `"value": ""` (the value is ignored).
- `isAnyOf` / `isNotAnyOf` — comma-separated string: `"value": "US,CA,MX"`.
- `before`, `beforeOrOn`, `on`, `after`, `afterOrOn` — calendar date `"value": "2026-01-31"`
  (`YYYY-MM-DD`).
- `between` / `notBetween` — stringified JSON with exactly `from` and `to`:
  `"value": "{\"from\":\"2026-01-01\",\"to\":\"2026-01-31\"}"` (`from` ≤ `to`).
- `within` — stringified JSON with exactly `direction`, `value`, `unit`:
  `"value": "{\"direction\":\"last\",\"value\":30,\"unit\":\"days\"}"`. `direction` is `last` or
  `next`; `unit` is `minutes`, `hours`, or `days`; `value` is a non-negative integer.
  `before`/`beforeOrOn`/`after`/`afterOrOn` also accept this relative format (not `on`).

## Fetching project-specific values

Fields marked project-specific (`mediaSource`, `campaign`, `adGroup`, `ad`, `keyword`, `creative`,
`priceExperimentId`, `latestProduct`) and custom attributes only match values that exist in the
project's data. Fetch with `get-audience-filter-options`:

- `project_id` (required)
- `fields` (required, at least one) — any of the eight fields above, `customAttribute:{key}` for
  one custom attribute, or `customAttribute` to list every custom attribute key with its values.

A custom-attribute entry may come back with `cardinality_exceeded: true` — a value the user
stated verbatim can still be valid even if it is not in the list.

Fixed-value fields (`country`, `platform`, `status`, …) are not served by this tool — use the
tables above. Never guess project-specific values — a filter on a non-existent value silently
matches zero customers.

## Example: building a link

User wants: "Android customers acquired through Instagram" in project `proj56965ae1`.

Rule (both conditions in one group — AND):

```json
{
  "groups": [
    {
      "conditions": [
        { "field": "platform", "operator": "is", "value": "android" },
        { "field": "mediaSource", "operator": "is", "value": "Instagram" }
      ]
    }
  ]
}
```

Link:

```
https://app.revenuecat.com/projects/56965ae1/customer-lists/all-customers?filters=%7B%22groups%22%3A%5B%7B%22conditions%22%3A%5B%7B%22field%22%3A%22platform%22%2C%22operator%22%3A%22is%22%2C%22value%22%3A%22android%22%7D%2C%7B%22field%22%3A%22mediaSource%22%2C%22operator%22%3A%22is%22%2C%22value%22%3A%22Instagram%22%7D%5D%7D%5D%7D
```

User wants: "customers on iOS or Android who are currently trialing and were first seen in the
last 30 days".

```json
{
  "groups": [
    {
      "conditions": [
        { "field": "platform", "operator": "isAnyOf", "value": "iOS,android" },
        { "field": "isCurrentlyTrialing", "operator": "is", "value": "true" },
        {
          "field": "firstSeenAt",
          "operator": "within",
          "value": "{\"direction\":\"last\",\"value\":30,\"unit\":\"days\"}"
        }
      ]
    }
  ]
}
```
