Audience Filters


An audience filter describes who a broadcast goes to — your groups, tags, contact profile, custom fields and link-click activity — instead of listing recipients one by one. The same filter is used to send a broadcast now (POST /api/v1/messaging/broadcast/audience) and to schedule one.

"audience": {
  "filter": {
    "contact": {
      "groups": { "ids": { "in": ["grp_7L1uzUXqj3eJvAwHf"] } },
      "address": { "state": { "in": ["TX", "FL"] } }
    }
  }
}

Endpoints

MethodPathPurposeRate limit
POST/api/v1/messaging/broadcast/audienceSend a broadcast to an audience now1 / minute
POST/api/v1/messaging/broadcast/audience/estimateCount how many contacts an audience reaches5 / minute
POST/api/v1/messaging/broadcast/schedulesSchedule a broadcast10 / minute

All three take the same audience object.


Who can ever be in an audience

A filter only ever narrows this set — it cannot widen it:

  • the contact is a confirmed member of at least one of your groups (a pending double opt-in that was never confirmed does not count), and
  • the contact has not opted out.

Each contact receives the broadcast once, even when it matches through several groups.


How a filter is written

  • A filter is a JSON object with two namespaces: contact (who the contact is) and activity (what the contact did).
  • Every condition is field: { operator: value }.
  • Everything is AND-ed: sibling fields, and multiple operators on one field.
  • in and nin take arrays (1–20 values). A list value cannot contain a comma.
  • Text values are 1–200 characters.
  • or and not are reserved and not supported yet.
  • At most 50 conditions per filter. An empty filter is rejected.
  • search (next to filter) is free text matched against phone number, first name, last name and email.

Operators

OperatorMeaningValue
eqequalsa value
in / ninis one of / is none ofarray of values
gte / lteat least / at mostnumber or date
between / notBetweenwithin / outside a date range[from, to]
containscase-insensitive substring matchtext
existsthe field has a value (true) or not (false)boolean

Each field accepts only the operators listed for it below. Dates are ISO 8601; a date-only upper bound of between covers that whole day. A range's from must not be after its to.


contact fields

FieldOperatorsValue
idsin, nincontact ids (con_…)
firstName, lastName, emaileq, containstext
phoneNumbereq, nin, containsE.164 (+18005551234); contains takes any part of it
gendereq, inMALE, FEMALE, OTHER
birthDate, createdAteq, gte, lteISO 8601 date
birthMontheq, in, gte, lte1–12
isFavorite, hasNameeqboolean
address.stateeq, in, nin, contains2-letter state code, uppercase (TX)
address.city, address.postalCodeeq, in, containstext
phone.areaCodeeq, in, ninUS/Canada 3-digit area code
timezone.nameeq, in, ninIANA timezone
timezone.isSeteqboolean
groups.idseq, in, ningroup ids (grp_…)
groups.optInAteq, gte, lte, between, notBetweenISO 8601 date
groups.tagIdsin, nintag ids (tag_…)
customFields.<label>eq, in, nin, gte, lte, exists, containskeyed by the field's label, as on the contact

The conditions under groups all hold on the same membership: { "ids": { "eq": "grp_A" }, "tagIds": { "nin": ["tag_X"] } } means "members of group A whose membership in A is not tagged X".

activity fields

FieldOperatorsValue
clicks.linkIdseq, intracking link ids (tlk_…)
clicks.dayeq, gte, lteYYYY-MM-DD (UTC)
clicks.none—{ "from", "to", "linkIds"? } — contacts with no click in the window

The clicks conditions hold on the same day: { "linkIds": { "eq": "tlk_A" }, "day": { "gte": "2026-09-01" } } means "clicked link A on or after September 1".


Sending to an audience

POST /api/v1/messaging/broadcast/audience

FieldRequiredDescription
audienceYes{ "filter", "search"? } — see above.
fromYesYour sending number (E.164) or short code.
bodyFor SMSMessage text; supports merge tags and tracking links.
mediaUrlFor MMSPublic URL of the media (max 1 MB).
waveConfigNo{ "recipientsPerWave", "waveIntervalMinutes" } — send in waves (interval at least 5 minutes).
idempotencyKeyNo2–100 characters; a repeat returns the original broadcast instead of sending twice.

Response 201:

{
  "id": "brc_DsAdb4EBYBh3z25sBA1qnv",
  "from": "+18005559876",
  "body": "Hi {{contact_first_name}}! {{trackingLink \"go.acme.com/spring\"}}",
  "totalRecipients": 4812,
  "status": "queued",
  "createdAt": "2026-09-30T10:00:00.000Z"
}

Errors specific to sending: 33039 the audience matches nobody, 33012 the audience exceeds your remaining daily quota, 33013 duplicate idempotencyKey, 33023 insufficient credits — plus the audience errors below and the content / sender codes of sending a message.


Estimate before you send

POST /api/v1/messaging/broadcast/audience/estimate takes the exact audience you would send, validates it the same way, and returns how many contacts it reaches right now — nothing is sent:

{ "audience": { "filter": { "contact": { "groups": { "ids": { "in": ["grp_7L1uzUXqj3eJvAwHf"] } } } } } }
{ "count": 12480, "exact": true }

For a very large audience counting stops early and exact is false — read count as "more than". Limited to 5 requests per minute — each estimate counts the whole audience.


Validation

Every id (grp_, tag_, con_, tlk_) and every custom-field label must belong to your account — otherwise 33041, with the exact path. A field or operator that is not in the tables above, or a value of the wrong type, is 33040 with the exact path. See Error codes.


Examples

Two groups:

{ "contact": { "groups": { "ids": { "in": ["grp_7L1uzUXqj3eJvAwHf", "grp_7L1uzUXqj3eJvAwHg"] } } } }

Gold-tier VIPs in Texas who joined this year, except one tag:

{
  "contact": {
    "address": { "state": { "eq": "TX" } },
    "groups": {
      "ids": { "eq": "grp_7L1uzUXqj3eJvAwHf" },
      "optInAt": { "gte": "2026-01-01" },
      "tagIds": { "nin": ["tag_DIB4BJaTlZ9EdWpIAZt54w"] }
    },
    "customFields": { "Tier": { "eq": "Gold" } }
  }
}

Re-engage members who did not click your last link in two weeks — then send them the link again:

{
  "audience": {
    "filter": {
      "contact": { "groups": { "ids": { "in": ["grp_7L1uzUXqj3eJvAwHf"] } } },
      "activity": {
        "clicks": {
          "none": {
            "from": "2026-09-14T00:00:00Z",
            "to": "2026-09-28T00:00:00Z",
            "linkIds": ["tlk_DoNjf7ed4zAZmYEY76kpsV"]
          }
        }
      }
    }
  },
  "from": "+18005559876",
  "body": "Still thinking it over, {{contact_first_name}}? {{trackingLink \"go.acme.com/spring\"}}"
}

Two conditions — both must hold:

{
  "contact": {
    "groups": { "ids": { "eq": "grp_7L1uzUXqj3eJvAwHf" } },
    "phone": { "areaCode": { "in": [512, 737] } }
  }
}

Did this page help you?