> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tracklysms.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Audience Filter DSL

> Reference for the audience filter query language

The Audience Filter DSL is a structured query language used to define audience segments. Filters are passed as JSON objects when creating or updating audiences via the API.

Every audience requires a **filter group** at the top level. Groups can contain conditions, nested groups, or both — enabling arbitrarily complex logic.

## Filter Group Structure

A filter group combines conditions with a logical operator.

```json theme={null}
{
  "operator": "AND" | "OR",
  "conditions": [...],
  "groups": [...]
}
```

| Field        | Type   | Required | Description                     |
| ------------ | ------ | -------- | ------------------------------- |
| `operator`   | string | Yes      | Logical operator: `AND` or `OR` |
| `conditions` | array  | No       | Array of condition objects      |
| `groups`     | array  | No       | Array of nested filter groups   |

<Warning>
  Every filter group must contain at least one condition or one nested group. An empty group will be rejected with the `empty_filter_group` error.
</Warning>

***

## Condition Types

Each condition object must include a `condition_type` field that determines which fields and operators are valid.

<Note>
  Requests use the snake\_case key `condition_type`. In API **responses**, condition objects are serialized with camelCase keys — `conditionType`, and (always present) `listId`. Optional keys `joinOperator`, `secondaryOperator`, `secondaryValue`, and `secondaryUnit` appear only when set.
</Note>

### 1. Time Conditions

Filter contacts by time-based fields using relative or absolute time windows.

```json theme={null}
{
  "condition_type": "time",
  "field": "last_clicked_at",
  "operator": "within",
  "value": 7,
  "unit": "days"
}
```

**Valid Fields:**

| Field             | Description                                              |
| ----------------- | -------------------------------------------------------- |
| `last_clicked_at` | Timestamp of the contact's most recent click             |
| `last_sent_at`    | Timestamp of the most recent message sent to the contact |
| `signup_date`     | Timestamp when the contact was added                     |

**Valid Operators:**

| Operator     | Description                               | Requires `value` + `unit` |
| ------------ | ----------------------------------------- | ------------------------- |
| `within`     | Field date is within the last N units     | Yes                       |
| `not_within` | Field date is NOT within the last N units | Yes                       |
| `before`     | Field date is before N units ago          | Yes                       |
| `after`      | Field date is after N units ago           | Yes                       |
| `exists`     | Field has any value (is not null)         | No                        |
| `not_exists` | Field is null or missing                  | No                        |

**Valid Units:** `days`, `hours`, `minutes`

<Note>
  The `exists` and `not_exists` operators do not require `value` or `unit`. All other operators require both.
</Note>

***

### 2. Count Conditions

Filter contacts by numeric count fields such as send count, click count, or conversion count.

```json theme={null}
{
  "condition_type": "count",
  "field": "click_count",
  "operator": "gte",
  "value": 3
}
```

**Valid Fields:**

| Field              | Description                                           |
| ------------------ | ----------------------------------------------------- |
| `send_count`       | Total number of messages sent to the contact          |
| `click_count`      | Total number of clicks by the contact                 |
| `conversion_count` | Total number of conversions attributed to the contact |

**Valid Operators:**

| Operator | Description              |
| -------- | ------------------------ |
| `gt`     | Greater than             |
| `gte`    | Greater than or equal to |
| `lt`     | Less than                |
| `lte`    | Less than or equal to    |
| `eq`     | Equal to                 |
| `ne`     | Not equal to             |

***

### 3. Custom Field Conditions

Filter contacts by any custom field stored on the contact record. Custom fields are arbitrary key-value pairs you define when creating or updating contacts.

```json theme={null}
{
  "condition_type": "custom_field",
  "field": "state",
  "operator": "eq",
  "value": "CA"
}
```

**Valid Fields:** Any custom field key that exists on your contacts.

**Valid Operators:**

| Operator     | Description                               | Requires `value` |
| ------------ | ----------------------------------------- | ---------------- |
| `eq`         | Equal to                                  | Yes              |
| `ne`         | Not equal to                              | Yes              |
| `in`         | Matches any value in an array             | Yes (array)      |
| `not_in`     | Does not match any value in an array      | Yes (array)      |
| `contains`   | Field value contains the substring        | Yes              |
| `gt`         | Greater than (numeric fields)             | Yes              |
| `gte`        | Greater than or equal to (numeric fields) | Yes              |
| `lt`         | Less than (numeric fields)                | Yes              |
| `lte`        | Less than or equal to (numeric fields)    | Yes              |
| `exists`     | Field has any value                       | No               |
| `not_exists` | Field is null or missing                  | No               |

***

### 4. Carrier Conditions

Filter contacts by their mobile carrier.

```json theme={null}
{
  "condition_type": "carrier",
  "field": "carrier",
  "operator": "in",
  "value": ["T-Mobile", "AT&T"]
}
```

**Valid Fields:** `carrier`

**Valid Operators:**

| Operator     | Description                     | Requires `value` |
| ------------ | ------------------------------- | ---------------- |
| `eq`         | Equal to a single carrier       | Yes              |
| `in`         | Matches any carrier in an array | Yes (array)      |
| `exists`     | Carrier data is present         | No               |
| `not_exists` | Carrier data is missing         | No               |

***

### 5. Timezone Conditions

Filter contacts by their timezone.

```json theme={null}
{
  "condition_type": "timezone",
  "field": "timezone",
  "operator": "in",
  "value": ["America/New_York", "America/Chicago"]
}
```

**Valid Fields:** `timezone`

**Valid Operators:**

| Operator | Description                      |
| -------- | -------------------------------- |
| `eq`     | Equal to a single timezone       |
| `in`     | Matches any timezone in an array |

Timezone values use the IANA timezone database format (e.g., `America/New_York`, `US/Pacific`, `Europe/London`).

***

### 6. Revenue Conditions

Filter contacts by revenue data.

```json theme={null}
{
  "condition_type": "revenue",
  "field": "revenue_total",
  "operator": "gte",
  "value": 10.00
}
```

**Valid Fields:**

| Field           | Description                                    |
| --------------- | ---------------------------------------------- |
| `has_revenue`   | Boolean — whether the contact has any revenue  |
| `revenue_total` | Total revenue amount attributed to the contact |

**Valid Operators:**

| Operator | Description              |
| -------- | ------------------------ |
| `eq`     | Equal to                 |
| `gte`    | Greater than or equal to |
| `lte`    | Less than or equal to    |
| `gt`     | Greater than             |
| `lt`     | Less than                |

***

### 7. Phone Numbers Conditions

Filter contacts by matching against an explicit list of phone numbers.

```json theme={null}
{
  "condition_type": "phone_numbers",
  "field": "phone",
  "operator": "in",
  "value": ["+15551234567", "+15559876543"]
}
```

<Warning>
  The `field` key **must be present** on a `phone_numbers` condition. Its value is not used for matching, but the underlying model requires the field — omitting it currently causes a `500` error rather than a validation error. Send any non-empty string (e.g. `"phone"`).
</Warning>

**Valid Fields:** The `field` value is not used for matching, but `field` must be present (any non-empty string).

**Valid Operators:**

| Operator | Description                                        | Requires `value`      |
| -------- | -------------------------------------------------- | --------------------- |
| `in`     | Contact's phone number is in the provided list     | Yes (non-empty array) |
| `not_in` | Contact's phone number is not in the provided list | Yes (non-empty array) |

`value` must be a non-empty array of phone numbers. An empty or missing list is rejected with `missing_phone_numbers_value`.

***

## Nesting Groups

Groups can be nested to create complex boolean logic. Each nested group has its own `operator` and set of conditions.

**Example:** Find contacts who were sent a message AND clicked within 30 days, AND whose carrier is either T-Mobile OR AT\&T.

```json theme={null}
{
  "operator": "AND",
  "conditions": [
    {
      "condition_type": "time",
      "field": "last_clicked_at",
      "operator": "within",
      "value": 30,
      "unit": "days"
    },
    {
      "condition_type": "count",
      "field": "send_count",
      "operator": "gte",
      "value": 1
    }
  ],
  "groups": [
    {
      "operator": "OR",
      "conditions": [
        {
          "condition_type": "carrier",
          "field": "carrier",
          "operator": "eq",
          "value": "T-Mobile"
        },
        {
          "condition_type": "carrier",
          "field": "carrier",
          "operator": "eq",
          "value": "AT&T"
        }
      ]
    }
  ]
}
```

This evaluates as:

```
(last_clicked_at within 30 days AND send_count >= 1)
AND
(carrier = "T-Mobile" OR carrier = "AT&T")
```

You can nest groups multiple levels deep. There is no hard limit on nesting depth, but deeply nested filters will take longer to evaluate.

***

## Validation Error Codes

If your filter is malformed, the API returns a `400` response with one of the following error codes:

| Error Code                       | Description                                                 |
| -------------------------------- | ----------------------------------------------------------- |
| `missing_filter`                 | No filter object was provided                               |
| `invalid_group_operator`         | `operator` must be `AND` or `OR`                            |
| `empty_filter_group`             | Group has no conditions and no nested groups                |
| `invalid_condition_type`         | `condition_type` is not one of the seven valid types        |
| `invalid_time_field`             | Time condition `field` is not valid                         |
| `invalid_time_operator`          | Time condition `operator` is not valid                      |
| `invalid_time_unit`              | Time condition `unit` must be `days`, `hours`, or `minutes` |
| `missing_time_value`             | Time condition is missing `value` or `unit`                 |
| `invalid_count_field`            | Count condition `field` is not valid                        |
| `invalid_count_operator`         | Count condition `operator` is not valid                     |
| `missing_count_value`            | Count condition is missing `value`                          |
| `missing_custom_field_name`      | Custom field condition is missing `field`                   |
| `invalid_custom_field_operator`  | Custom field condition `operator` is not valid              |
| `missing_custom_field_value`     | Custom field condition is missing `value`                   |
| `invalid_carrier_operator`       | Carrier condition `operator` is not valid                   |
| `missing_carrier_value`          | Carrier condition is missing `value`                        |
| `invalid_timezone_operator`      | Timezone condition `operator` is not valid                  |
| `missing_timezone_value`         | Timezone condition is missing `value`                       |
| `invalid_revenue_field`          | Revenue condition `field` is not valid                      |
| `invalid_revenue_operator`       | Revenue condition `operator` is not valid                   |
| `missing_revenue_value`          | Revenue condition is missing `value`                        |
| `invalid_phone_numbers_operator` | Phone numbers condition `operator` is not `in` or `not_in`  |
| `missing_phone_numbers_value`    | Phone numbers condition `value` is missing or an empty list |

## Next Steps

<CardGroup cols={2}>
  <Card title="Creating Audiences" icon="users" href="/guides/audiences/creating-audiences">
    Build audience segments in the UI
  </Card>

  <Card title="Create Audience" icon="users" href="/api-reference/v2/audiences/create-audience">
    Create an audience via the API
  </Card>
</CardGroup>
