Target an audience
Preview and save a filter definition, then send to it by ID.
Send to your user IDs
If devices already carry your external_id, you can target those users directly with a secret key:
{
"audience": { "external_ids": ["user-42", "user-73"] },
"payload": { "title": "Your order is ready" }
}Send this body to POST /v1/messages. The list accepts 1–2,000 unique IDs,
with 1–256 characters per ID. Choose exactly one audience shape per request.
All matching devices in this app are selected when sending starts. Unknown IDs
are ignored; no matches still returns 202, followed by a trace with zero deliveries.
Opted-out or invalid devices produce failed deliveries; live sends exclude test devices.
The response's audience.size counts submitted user IDs, not devices. deliveries
is empty until the worker resolves the audience; use GET /v1/messages/{id} to check results.
This targeting also works with scheduled sends,
including local-time waves.
Use a saved audience
Prerequisites
Use a secret key from the app, or a session token, to manage audiences. Listing
and preview require audiences:read; creation and deletion require audiences:write.
New secret keys include these scopes. If an older key lacks them, create a new key.
Use a secret key with messages:send for sending.
See authentication. Replace the example audience ID with the ID returned by creation.
The following definition selects iOS devices active within 30 days with tags.plan equal to pro.
Samples on this page show placeholders. Sign in to the dashboard in this browser and they fill in with your own organization, app and mobile key.
1. Count before you save
Preview counts a definition you are still assembling. Nothing is stored.
curl -X POST https://api.carillon.dev/v1/apps/your_app_id/audiences/preview \-H "Authorization: Bearer carillon_sk_live_your_secret_key" \-H "Content-Type: application/json" \-d '{ "definition": { "filters": [ { "field": "platform", "value": "ios" }, { "field": "last_active", "within_days": 30 }, { "field": "tag", "key": "plan", "value": "pro" } ] }}'{ "reachable": 1284 }Every filter narrows, so adding one can only make that number smaller.
2. Save it
curl -X POST https://api.carillon.dev/v1/apps/your_app_id/audiences \-H "Authorization: Bearer carillon_sk_live_your_secret_key" \-H "Content-Type: application/json" \-d '{ "name": "Active iOS Pro", "definition": { "filters": [ { "field": "platform", "value": "ios" }, { "field": "last_active", "within_days": 30 }, { "field": "tag", "key": "plan", "value": "pro" } ] }}'{
"id": "01937b1e-0000-7000-8000-000000000000",
"name": "Active iOS Pro",
"definition": { "filters": [ { "field": "platform", "value": "ios" }, { "field": "last_active", "within_days": 30 }, { "field": "tag", "key": "plan", "value": "pro" } ] },
"created_at": "2026-08-20T09:02:11.004Z"
}The name is unique within the app, so a colliding one answers 409 duplicate_audience_name.
3. Send to it
curl -X POST https://api.carillon.dev/v1/messages \-H "Authorization: Bearer carillon_sk_live_your_secret_key" \-H "Content-Type: application/json" \-H "Idempotency-Key: 018f2c8a-6f3f-7a2b-9f1e-2c9b0f4d5a12" \-d '{ "audience": { "audience_id": "01937b1e-0000-7000-8000-000000000000" }, "payload": { "title": "We saved your spot", "body": "Come back and pick up where you left off." }}'deliveries comes back empty, as it does for { "all": true }: the audience is resolved by the
worker, so the identifiers are minted then. Follow the campaign through its
trace.
The campaign keeps its own copy of the filters
Editing or deleting "Active iOS Pro" tomorrow cannot change who this campaign goes to. The filter set is copied onto the campaign as it is accepted; only the devices it resolves to are recomputed at send time.
Listing and deleting
curl https://api.carillon.dev/v1/apps/your_app_id/audiences \-H "Authorization: Bearer carillon_sk_live_your_secret_key"Deleting is a DELETE on the audience and answers 204. Campaigns already sent to it are
untouched.