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).
67
80%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./revenuecat/skills/revenuecat-audiences/SKILL.mdUse 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.
Audiences filter on the fields below, including:
latestProduct, allPurchasedProductIds, latestPurchasedOffering,
entitlements, offerstotalSpent (filter, and sort_by: total_spent), totalRenewals (filter
only; there is no renewal sort)status, trial, auto-renew intent, ownershipplatform, latestStore, country, storefrontcustomAttribute:{key}Map the question onto those fields, filter, then read a sample.
get-audience-filter-options for any field marked
project-specific that the question uses (project_id, fields).preview-audience with project_id and body.rules in 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.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.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").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.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.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) only when no tool result gave you a URL.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.
list-projects. For dashboard URLs, strip the proj prefix.get-audience-filter-options first.filters query param on
https://app.revenuecat.com/projects/{project_id}/customer-lists/all-customers.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.all-customers list. The unfiltered Audiences home is
/projects/{project_id}/customer-lists.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/list7c1f0a2b93Wrong (the audience id instead of the customer_list_id):
https://app.revenuecat.com/projects/56965ae1/customer-lists/audf0269cdf3df84dd2Do 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.
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 filters value is URL-encoded JSON with this shape:
{
"groups": [
{
"conditions": [{ "field": "platform", "operator": "is", "value": "android" }]
}
]
}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).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%7DWrong (raw JSON, spaces, unencoded quotes/braces):
?filters={"groups": [{"conditions": [...]}]}import json
from urllib.parse import quote
rule = {
"groups": [
{"conditions": [{"field": "platform", "operator": "is", "value": "android"}]},
]
}
print(quote(json.dumps(rule, separators=(",", ":")), safe=""))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.
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 |
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 |
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) |
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 |
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 |
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.
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).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.
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%7DUser 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\"}"
}
]
}
]
}ccfc038
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.