Audience filters and dashboard links
Use Audiences to filter the user's customers and to share a dashboard link to that set. The same filters answer "who" questions — they are not a full leaderboard sort.
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 below, including:
- Product and duration —
latestProduct,allPurchasedProductIds,latestPurchasedOffering, entitlements, offers - Spend and renewals —
totalSpent,totalRenewals(thresholds, not a 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.
- Fetch project-specific values with
get-audience-filter-optionsfor any field marked project-specific that the question uses (project_id,fields). - Reuse an existing audience from
list-audiencesif one already matches. Otherwisecreate-audiencewith the samegroups/conditionsrule shape as the filter rule. Body is onlynameandrules.create-audiencepersists a saved audience, so get explicit confirmation first. get-audiencewithexpand: ["customer_sample"]. Sample rows includetotal_spent, status, and latest product — not every filter field. Rank or name customers only from fields the sample returned.- Number fields like
totalRenewalsare filters, not sample columns. Filter on a high threshold and report the sample; do not invent a value that was not returned. - Say it is a sample of matches, not an exhaustive ranking of every customer. Filtering and a
customer_samplecannot prove a global superlative (who renewed or spent the most). After you try the steps above, say that: you can show high-threshold matches, not name a unique maximum. - Share the dashboard link (constructing a link or linking to a saved audience).
Constructing a link
Two shapes: a filtered all-customers link for ad-hoc exploration, and a saved-audience link
after create-audience.
- Get the project ID from
list-projects. For dashboard URLs, strip theprojprefix. - Pick fields and operators from the field tables. Do not invent field or operator names.
- For project-specific fields, fetch valid values with
get-audience-filter-optionsfirst. - Assemble the rule JSON: one group per OR-branch, conditions inside a group for AND, values encoded per value formats.
- Serialize and URL-encode the rule with a short script (see encoding the rule) — do not encode by hand.
- Append it as the
filtersquery param onhttps://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 fromlist-projectswithprojstripped.- Filters only work on the
all-customerslist. The unfiltered Audiences home is/projects/{project_id}/customer-lists.
Linking to a saved audience
Link with customer_list_id — the field create-audience, get-audience, and list-audiences
return 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:
{
"groups": [
{
"conditions": [{ "field": "platform", "operator": "is", "value": "android" }]
}
]
}
- Conditions within a group combine with AND.
- Groups combine with OR.
valueis 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).
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
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.
Text fields
Operators: is, isNot, contains, doesNotContain, isEmpty, isNotEmpty.
| Field | Meaning |
|---|---|
customerId | App user ID |
originalAppUserId | Original app user ID |
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 |
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 exactlyfromandto:"value": "{\"from\":\"2026-01-01\",\"to\":\"2026-01-31\"}"(from≤to).within— stringified JSON with exactlydirection,value,unit:"value": "{\"direction\":\"last\",\"value\":30,\"unit\":\"days\"}".directionislastornext;unitisminutes,hours, ordays;valueis a non-negative integer.before/beforeOrOn/after/afterOrOnalso accept this relative format (noton).
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, orcustomAttributeto 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):
{
"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".
{
"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\"}"
}
]
}
]
}