# Audiences

Pick who gets a campaign or broadcast with filters on tags, attributes, signup date, opens, clicks and other campaigns.

An audience says who a campaign or broadcast is for. It is a list of filter groups:

- A contact matches when it passes **every** filter of **at least one** group. Filters in a group are joined by AND, groups by OR.
- No groups at all means every contact in the project.
- Up to 10 groups of 20 filters.

## Filters

| type | op | other fields | Matches a contact that |
| --- | --- | --- | --- |
| `tag` | `has`, `has_not` | `tag` | has, or lacks, the tag |
| `attribute` | `equals`, `not_equals` | `key`, `value` | has, or lacks, that value for the key |
| `added_days_ago` | `less_than`, `more_than` | `days` | was added within, or before, that many days |
| `opened` | `within`, `not_within` | `days` | opened any email in that many days, or did not |
| `clicked` | `within`, `not_within` | `days` | clicked any email in that many days, or did not |
| `campaign` | `is_enrolled`, `is_not_enrolled`, `was_enrolled`, `was_never_enrolled` | `campaign_id` | is in that campaign now, or ever was |

For example, “trial users added in the last two weeks, or anyone tagged `beta`”:

```json Two filter groups
[
  {
    "filters": [
      { "type": "tag", "op": "has", "tag": "trial" },
      { "type": "added_days_ago", "op": "less_than", "days": 14 }
    ]
  },
  { "filters": [{ "type": "tag", "op": "has", "tag": "beta" }] }
]
```

## When matching happens

- A live campaign checks a contact when it is added or its tags or attributes change, within seconds.
- Setting a campaign live, or changing a live campaign’s audience, checks every contact.
- Filters that depend on time (`added_days_ago`, `opened`, `clicked`) are checked again every 60 minutes.
- A person who stops matching leaves the campaign (exit reason `filter_mismatch`).
- A broadcast checks its audience once, when it sends.

## Preview before you go live

Count who an audience matches right now, with a few of them. Large projects are counted in parts: while `is_complete` is false, send the returned `cursor` back and add the counts up.

```sh Preview an audience
curl -X POST "https://sendocado.com/api/v1/projects/$PROJECT_ID/audience-previews" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "filter_groups": [
    {
      "filters": [
        {
          "type": "tag",
          "op": "has",
          "tag": "trial"
        },
        {
          "type": "added_days_ago",
          "op": "less_than",
          "days": 14
        }
      ]
    }
  ],
  "cursor": null
}'
```

```json Response
{
  "matched_count": 158,
  "scanned_count": 2418,
  "is_complete": true,
  "cursor": null,
  "sample": [
    {
      "id": "j8ht9lgmxg9edn581u33xtplpft75v2s",
      "email": "maya@example.com",
      "first_name": "Maya",
      "last_name": "Patel"
    }
  ]
}
```
