# Audience event query filter

Search and aggregate audience events with a structured JSON filter. Group by event type, filter on parameters, run date histograms and sum metrics across the last N days.

The **Event Query Filter** is a structured JSON filter posted to `/project/{project}/event/search` (and related event endpoints) to find events by type, parameters, creation date or any native event attribute. It shares the overall shape of the [Audience query filter](/product-api/audience-query-filter) but is specific to the event collection — the condition grammar is narrower and the aggregation surface is richer (date histograms, sums, metrics).

> **Note**: Event parameters live under the event type namespace. For a `create` event you filter on `create.source`; for an order you filter on `ecommerce_order_create.order-euro-amount`. Parameter keys are auto-resolved against the event type configuration of your project.

## Quickstart

### All `create` events from the last 30 days

```json
{
  "version": "0.0.1",
  "root": {
    "type": "group",
    "join": "and",
    "children": [
      {
        "type": "event_condition",
        "key": "event-type",
        "operator": "matches-string",
        "values": ["create"]
      },
      {
        "type": "event_condition",
        "key": "created-at",
        "operator": "range-date-relative",
        "values": {
          "lowerOffset": -30,
          "upperOffset": null,
          "lowerExcludeEquals": false,
          "upperExcludeEquals": false,
          "lowerRounding": false,
          "upperRounding": false,
          "lowerOffsetPeriod": "day",
          "upperOffsetPeriod": "day"
        }
      }
    ]
  },
  "limit": 10,
  "offset": 0
}
```

### Ecommerce orders between €100 and €200

```json
{
  "version": "0.0.1",
  "root": {
    "type": "group",
    "join": "and",
    "children": [
      {
        "type": "event_condition",
        "key": "event-type",
        "operator": "matches-string",
        "values": ["ecommerce_order_create"]
      },
      {
        "type": "event_condition",
        "key": "ecommerce_order_create.order-euro-amount",
        "operator": "range-number",
        "values": {
          "lowerNumber": 100.0,
          "upperNumber": 200.0
        }
      }
    ]
  },
  "limit": 10,
  "offset": 0
}
```

## Basic concepts

### Filter structure

- **`root`** — the main filter condition, typically a `group` of event conditions.
- **`limit`** — maximum number of results to return.
- **`offset`** — number of results to skip.
- **`sortField`** — field to sort by (optional).
- **`sortAsc`** — sort direction: `true` for ascending, `false` for descending (optional).

### Condition types

- **`event_condition`** — filter by a native event attribute or an event parameter.
- **`group`** — combine multiple conditions with AND/OR logic (`join: "and" | "or"`).

### Native event attributes

Available directly as `key` in an `event_condition`:

| Attribute                | Meaning                                                            |
| ------------------------ | ------------------------------------------------------------------ |
| `bucket`                 | Bucket number.                                                     |
| `received-at`            | When the event was received.                                       |
| `created-at`             | When the event was created.                                        |
| `event-source`           | Source of the event.                                               |
| `event-type`             | Type of event (e.g. `create`, `update`, `ecommerce_order_create`). |
| `audience-id`            | ID of the audience contact.                                        |
| `audience-ids`           | Multiple audience contact IDs.                                     |
| `audience-ds-ids`        | Audience datasource IDs.                                           |
| `audience-categories`    | Audience categories.                                               |
| `audience-target-groups` | Audience target groups.                                            |
| `ds-contact-id`          | Datasource contact ID.                                             |
| `ds-id`                  | Datasource ID.                                                     |
| `ds-event-id`            | Datasource event ID.                                               |

### Event parameters

Filter by event-specific parameters by prefixing the key with the event type. Examples:

- `create.source` — the `source` parameter of `create` events.
- `ecommerce_order_create.order-euro-amount` — the `order-euro-amount` parameter of `ecommerce_order_create` events.

Parameter keys are auto-resolved based on the event type configuration of the project.

## Common patterns

### By event type and source

```json
{
  "version": "0.0.1",
  "root": {
    "type": "group",
    "join": "and",
    "children": [
      {
        "type": "event_condition",
        "key": "event-type",
        "operator": "matches-string",
        "values": ["create"]
      },
      {
        "type": "event_condition",
        "key": "create.source",
        "operator": "matches-string",
        "values": ["datasource|_instasent"]
      }
    ]
  },
  "limit": 10,
  "offset": 0,
  "sortField": "create.source",
  "sortAsc": true
}
```

### Date range

```json
{
  "version": "0.0.1",
  "root": {
    "type": "group",
    "children": [
      {
        "type": "event_condition",
        "key": "created-at",
        "operator": "range-date-relative",
        "values": {
          "lowerOffset": -30,
          "upperOffset": null,
          "lowerExcludeEquals": false,
          "upperExcludeEquals": false,
          "lowerRounding": false,
          "upperRounding": false,
          "lowerOffsetPeriod": "day",
          "upperOffsetPeriod": "day"
        }
      }
    ]
  },
  "limit": 10,
  "offset": 0
}
```

### Events for specific contacts

```json
{
  "version": "0.0.1",
  "root": {
    "type": "group",
    "children": [
      {
        "type": "event_condition",
        "key": "audience-id",
        "operator": "matches-string",
        "values": ["0aCcUIewHALKk9jpaS9QaazifbmUr8fB"]
      }
    ]
  },
  "limit": 100,
  "offset": 0
}
```

### Additional filter options

Top-level filters that narrow the result set without going through `root`:

| Field                         | Effect                                                       |
| ----------------------------- | ------------------------------------------------------------ |
| `filterAudienceIds`           | Restrict to specific audience contact IDs.                   |
| `filterEventIds`              | Restrict to specific event IDs.                              |
| `filterEventIdsNot`           | Exclude specific event IDs.                                  |
| `filterDatasourceIds`         | Restrict to specific datasource IDs.                         |
| `filterAudienceDatasourceIds` | Restrict events for contacts from specific datasources.      |
| `filterSamplingPercent`       | Retrieve only a percentage of results (0–100, default: 100). |
| `filterBucketMin`             | Minimum bucket number (0–100, default: 0).                   |
| `filterBucketMax`             | Maximum bucket number (0–100, default: 100).                 |

Example:

```json
{
  "version": "0.0.1",
  "limit": 100,
  "offset": 0,
  "root": {
    "type": "group",
    "children": [
      {
        "type": "event_condition",
        "key": "event-type",
        "operator": "matches-string",
        "values": ["create"]
      }
    ]
  },
  "filterSamplingPercent": 33,
  "filterAudienceIds": ["0aCcUIewHALKk9jpaS9QaazifbmUr8fB"],
  "filterEventIdsNot": ["S9QaazifbmUr8fB0aCcUIewHALKk9jpa"]
}
```

## Cursor-based pagination

For large traverses, use cursor pagination instead of `offset`/`limit`. A cursor keeps a consistent snapshot of the data across requests.

### How it works

1. **First request** — run a query without a cursor. The response includes a `cursor` string.
2. **Subsequent requests** — pass the `cursor` from the previous response to continue.
3. **End of results** — when `cursor` is `null`, there are no more results.

### Cursor format

The cursor is a **base64-encoded string** containing a snapshot identifier, position information and a keep-alive setting. You do not need to parse or modify it — just pass it back as-is.

### Using cursors

```json
// First request
{
  "version": "0.0.1",
  "root": {
    "type": "group",
    "children": [
      {
        "type": "event_condition",
        "key": "event-type",
        "operator": "matches-string",
        "values": ["create"]
      }
    ]
  },
  "limit": 100
}

// Response includes: { "cursor": "eyJwb2ludEluVGltZUlkIjoiLi4uIn0=", ... }

// Next request — just pass the cursor
{
  "cursor": "eyJwb2ludEluVGltZUlkIjoiLi4uIn0="
}
```

> **Note**: - The cursor expires after a period of inactivity (default: 1–5 minutes).
> - `offset` is **not compatible** with cursor pagination and is cleared when a cursor is set.
> - Cursors are **stateless** — any process holding the cursor can continue the iteration.

## Aggregations

> **Warning**: Aggregations are **disabled by default** and are only available on endpoints that explicitly support them. Check the endpoint in the [API Reference](/product-api/reference) before using them. Access requires the `PROJECT_AGGREGATIONS` scope, which is manually granted by Instasent and reserved for trusted partners.

Aggregations analyse and summarise the filtered events. All configuration goes inside `params`; use the `@` prefix to reference event parameters — they are auto-resolved.

### Date histogram

Group events by time intervals:

```json
{
  "version": "0.0.1",
  "limit": 0,
  "offset": 0,
  "aggregations": {
    "eventsByDay": {
      "type": "date_histogram",
      "params": {
        "field": "@created-at",
        "calendar_interval": "1d",
        "format": "yyyy-MM-dd",
        "time_zone": "UTC"
      }
    }
  }
}
```

### Date histogram with nested aggregations

Calculate metrics for each time period:

```json
{
  "version": "0.0.1",
  "limit": 0,
  "offset": 0,
  "aggregations": {
    "salesByDate": {
      "type": "date_histogram",
      "params": {
        "field": "@ecommerce_order_create.created-at",
        "calendar_interval": "1d",
        "format": "yyyy-MM-dd (EEE)",
        "time_zone": "Europe/Madrid"
      },
      "aggs": {
        "totalSales": {
          "type": "sum",
          "params": { "field": "@ecommerce_order_create.order-euro-amount" }
        },
        "orderCount": {
          "type": "sum",
          "params": { "field": "@ecommerce_order_create.product-count" }
        }
      }
    }
  }
}
```

### Fixed interval vs calendar interval

- **`calendar_interval`** — calendar-aware intervals (`1d`, `1w`, `1M`, `1y`).
- **`fixed_interval`** — fixed time intervals (`1h`, `6h`, `30m`).

```json
{
  "version": "0.0.1",
  "limit": 0,
  "offset": 0,
  "aggregations": {
    "salesBy6HourInterval": {
      "type": "date_histogram",
      "params": {
        "field": "@ecommerce_order_create.created-at",
        "fixed_interval": "6h",
        "format": "HH:00",
        "time_zone": "UTC"
      },
      "aggs": {
        "salesAmount": {
          "type": "sum",
          "params": { "field": "@ecommerce_order_create.order-euro-amount" }
        }
      }
    }
  }
}
```

### Terms aggregation

Group events by unique values in a field:

```json
{
  "version": "0.0.1",
  "limit": 0,
  "offset": 0,
  "aggregations": {
    "eventTypes": {
      "type": "terms",
      "params": {
        "field": "@event-type",
        "size": 10
      }
    }
  }
}
```

### Terms with nested aggregations

Detailed metrics per category:

```json
{
  "version": "0.0.1",
  "limit": 0,
  "offset": 0,
  "aggregations": {
    "topProducts": {
      "type": "terms",
      "params": {
        "field": "@ecommerce_order_create.product-name",
        "size": 5,
        "missing": "unknown",
        "order": { "salesCount": "desc" }
      },
      "aggs": {
        "salesCount": {
          "type": "sum",
          "params": { "field": "@ecommerce_order_create.product-count" }
        },
        "salesEuroAmount": {
          "type": "sum",
          "params": { "field": "@ecommerce_order_create.product-price-euro" }
        }
      }
    }
  }
}
```

### Sum aggregation

Calculate totals across matching events:

```json
{
  "version": "0.0.1",
  "limit": 0,
  "offset": 0,
  "aggregations": {
    "totalRevenue": {
      "type": "sum",
      "params": { "field": "@ecommerce_order_create.order-euro-amount" }
    }
  }
}
```

### Complete sales analysis

Sales patterns across multiple dimensions:

```json
{
  "version": "0.0.1",
  "limit": 0,
  "offset": 0,
  "root": {
    "type": "group",
    "children": [
      {
        "type": "event_condition",
        "key": "event-type",
        "operator": "matches-string",
        "values": ["ecommerce_order_create"]
      },
      {
        "type": "event_condition",
        "key": "created-at",
        "operator": "range-date-relative",
        "values": {
          "lowerOffset": -21,
          "upperOffset": 0,
          "lowerOffsetPeriod": "day",
          "upperOffsetPeriod": "day"
        }
      }
    ]
  },
  "aggregations": {
    "totalSalesAmount": {
      "type": "sum",
      "params": { "field": "@ecommerce_order_create.order-euro-amount" }
    },
    "salesByDate": {
      "type": "date_histogram",
      "params": {
        "field": "@ecommerce_order_create.created-at",
        "calendar_interval": "1d",
        "format": "yyyy-MM-dd (EEE)",
        "time_zone": "UTC"
      },
      "aggs": {
        "salesCount": {
          "type": "sum",
          "params": { "field": "@ecommerce_order_create.product-count" }
        },
        "salesEuroAmount": {
          "type": "sum",
          "params": { "field": "@ecommerce_order_create.order-euro-amount" }
        }
      }
    },
    "topProducts": {
      "type": "terms",
      "params": {
        "field": "@ecommerce_order_create.product-name",
        "size": 10,
        "missing": "unknown",
        "order": { "salesCount": "desc" }
      },
      "aggs": {
        "salesCount": {
          "type": "sum",
          "params": { "field": "@ecommerce_order_create.product-count" }
        },
        "salesEuroAmount": {
          "type": "sum",
          "params": { "field": "@ecommerce_order_create.product-price-euro" }
        }
      }
    }
  }
}
```

### Listing event types in a project

```json
{
  "version": "0.0.1",
  "limit": 0,
  "aggregations": {
    "total": {
      "type": "terms",
      "params": {
        "field": "@event-type",
        "size": 100
      }
    }
  }
}
```

### Range aggregations

Group events into ranges:

```json
{
  "version": "0.0.1",
  "limit": 0,
  "offset": 0,
  "aggregations": {
    "contactsByBucketRange": {
      "type": "range",
      "params": {
        "field": "@bucket",
        "ranges": [
          { "from": 0,  "to": 33  },
          { "from": 33, "to": 66  },
          { "from": 66, "to": 100 }
        ]
      }
    }
  }
}
```

## Operators reference

Every condition node has the shape:

```json
{
  "type": "event_condition",
  "key": "<event attribute or parameter>",
  "operator": "<operator name>",
  "values": "<values — shape depends on the operator>"
}
```

The subsections below describe, for each operator, the exact shape of `values` and any behavioural notes. About `key`:

- A [native event attribute](#native-event-attributes) (e.g. `event-type`, `created-at`, `received-at`, `audience-id`, `ds-id`).
- An event parameter, scoped by event type: `<event-type>.<parameter>` — e.g. `ecommerce_order_create.order-euro-amount`, `create.source`. Parameters are resolved against the event-type configuration of the project, and their data type drives which operators are valid.

| Family     | Operators                                                                                                                                          |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Generic    | `match-all`, `match-none`, `exists`, `exists-not`                                                                                                  |
| Boolean    | `matches-bool`                                                                                                                                     |
| String     | `contains`, `contains-not`, `startswith`, `startswith-not`, `endswith`, `endswith-not`, `matches-string`, `matches-string-not`                     |
| Numeric    | `matches-number`, `matches-number-not`, `range-number`, `range-number-not`                                                                         |
| Date       | `matches-date`, `range-date`, `range-date-not`, `range-date-relative`, `range-date-anniversary`, `range-date-dayversary`, `range-date-timeversary` |
| Geographic | `geopoint-distance`                                                                                                                                |

Conventions used below:

- **Negation.** Every operator with a `-not` suffix takes the same `values` as its positive counterpart and inverts the match. A missing field is *not matched* by the positive operator and *is matched* by the negated one — i.e. negation is "not (positive match)", which includes events where the field is absent.
- **Multi-value fields + `&&` prefix.** For array-valued fields, string and numeric "list" operators default to OR semantics across the provided values. Passing `"&&"` as the first element of `values` switches to AND — *all* values must be present. `"||"` (the default) is also accepted explicitly.

### Generic

These work regardless of the field's data type.

#### `match-all`

Matches every event. Used internally. `values` is ignored.

#### `match-none`

Matches no events. Used internally. `values` is ignored.

#### `exists`

The field is present on the event (non-null, and non-empty for array fields).

```json
{ "type": "event_condition", "key": "ecommerce_order_create.coupon-code", "operator": "exists", "values": [] }
```

#### `exists-not`

The field is missing or null.

### Boolean

#### `matches-bool`

Exact boolean match. `values` must be an array with exactly one element: `[true]` or `[false]`.

```json
{ "type": "event_condition", "key": "ecommerce_order_create.is-first-order", "operator": "matches-bool", "values": [true] }
```

### String

Apply to fields of type `STRING`, `KEYWORD` and `TEXT`. All string operators accept an array of strings; the first element may optionally be `"&&"` or `"||"` to set the combination mode for multi-value fields (default `||` / OR).

| Operator             | Behaviour                   | Case             | Min length |
| -------------------- | --------------------------- | ---------------- | ---------- |
| `contains`           | Substring match (`*value*`) | Case-insensitive | 2 chars    |
| `contains-not`       | Inverse of `contains`       | Case-insensitive | 2 chars    |
| `startswith`         | Prefix match (`value*`)     | Case-insensitive | 1 char     |
| `startswith-not`     | Inverse of `startswith`     | Case-insensitive | 1 char     |
| `endswith`           | Suffix match (`*value`)     | Case-insensitive | 1 char     |
| `endswith-not`       | Inverse of `endswith`       | Case-insensitive | 1 char     |
| `matches-string`     | Exact value match           | Case-sensitive   | 1 char     |
| `matches-string-not` | Inverse of `matches-string` | Case-sensitive   | 1 char     |

Max value length for `contains` / `contains-not`: 128 chars.

OR across multiple values (default):

```json
{ "type": "event_condition", "key": "event-type", "operator": "matches-string", "values": ["create", "update"] }
```

AND across multiple values (for multi-value fields such as `audience-target-groups`):

```json
{
  "type": "event_condition",
  "key": "audience-target-groups",
  "operator": "matches-string",
  "values": ["&&", "vip", "newsletter"]
}
```

### Numeric

Apply to fields of type `INT` and `DECIMAL`.

#### `matches-number` / `matches-number-not`

Exact match against one or more numbers. Same `&&` / `||` semantics as the string operators.

```json
{
  "type": "event_condition",
  "key": "ecommerce_order_create.product-count",
  "operator": "matches-number",
  "values": [1, 2, 3]
}
```

#### `range-number` / `range-number-not`

Range query. `values` is an **object**, not an array:

```json
{
  "type": "event_condition",
  "key": "ecommerce_order_create.order-euro-amount",
  "operator": "range-number",
  "values": {
    "lowerNumber": 100,
    "upperNumber": 200,
    "lowerExcludeEquals": false,
    "upperExcludeEquals": false
  }
}
```

| Field                | Type           | Default | Meaning                                                                  |
| -------------------- | -------------- | ------- | ------------------------------------------------------------------------ |
| `lowerNumber`        | number \| null | `null`  | Lower bound. `null` = unbounded below.                                   |
| `upperNumber`        | number \| null | `null`  | Upper bound. `null` = unbounded above.                                   |
| `lowerExcludeEquals` | bool           | `false` | If `true`, lower bound is exclusive (`gt`); otherwise inclusive (`gte`). |
| `upperExcludeEquals` | bool           | `false` | If `true`, upper bound is exclusive (`lt`); otherwise inclusive (`lte`). |

Both bounds `null` on `range-number` matches everything (and nothing on `range-number-not`).

### Date

Apply to fields of type `DATE`. All date operators honour the project timezone.

#### `matches-date`

Exact day match. Internally expanded to `[00:00:00, 23:59:59]` in the project timezone.

```json
{
  "type": "event_condition",
  "key": "created-at",
  "operator": "matches-date",
  "values": { "date": "2026-04-22" }
}
```

| Field  | Type                  | Notes                  |
| ------ | --------------------- | ---------------------- |
| `date` | string (`YYYY-MM-DD`) | Required. Exactly one. |

#### `range-date` / `range-date-not`

Absolute date range.

```json
{
  "type": "event_condition",
  "key": "created-at",
  "operator": "range-date",
  "values": {
    "lowerDate": "2026-01-01",
    "upperDate": "2026-03-31",
    "lowerExcludeEquals": false,
    "upperExcludeEquals": false,
    "lowerRounding": true,
    "upperRounding": true
  }
}
```

| Field                | Type                      | Default | Meaning                                                           |
| -------------------- | ------------------------- | ------- | ----------------------------------------------------------------- |
| `lowerDate`          | ISO date/datetime \| null | `null`  | Lower bound. `null` = unbounded below.                            |
| `upperDate`          | ISO date/datetime \| null | `null`  | Upper bound. `null` = unbounded above.                            |
| `lowerExcludeEquals` | bool                      | `false` | Exclude equality on lower bound (`gt` vs `gte`).                  |
| `upperExcludeEquals` | bool                      | `false` | Exclude equality on upper bound (`lt` vs `lte`).                  |
| `lowerRounding`      | bool                      | `false` | If `true`, rounds the lower bound to the start of its day (`/d`). |
| `upperRounding`      | bool                      | `false` | If `true`, rounds the upper bound to the end of its day (`/d`).   |

#### `range-date-relative` / `range-date-relative-not`

Range expressed relative to *now*.

```json
{
  "type": "event_condition",
  "key": "created-at",
  "operator": "range-date-relative",
  "values": {
    "lowerOffset": -30,
    "upperOffset": 0,
    "lowerOffsetPeriod": "day",
    "upperOffsetPeriod": "day",
    "lowerExcludeEquals": false,
    "upperExcludeEquals": false,
    "lowerRounding": false,
    "upperRounding": false
  }
}
```

| Field                | Type        | Default | Meaning                                                                         |
| -------------------- | ----------- | ------- | ------------------------------------------------------------------------------- |
| `lowerOffset`        | int \| null | `null`  | Signed offset from now. Negative = past, positive = future. `null` = unbounded. |
| `upperOffset`        | int \| null | `null`  | Signed offset from now.                                                         |
| `lowerOffsetPeriod`  | enum        | `"day"` | Unit of `lowerOffset`. One of: `min`, `hour`, `day`, `week`, `month`, `year`.   |
| `upperOffsetPeriod`  | enum        | `"day"` | Unit of `upperOffset`. Same options.                                            |
| `lowerExcludeEquals` | bool        | `false` | Same semantics as `range-date`.                                                 |
| `upperExcludeEquals` | bool        | `false` | Same semantics as `range-date`.                                                 |
| `lowerRounding`      | bool        | `false` | Rounds the bound to the start of the period.                                    |
| `upperRounding`      | bool        | `false` | Rounds the bound to the end of the period.                                      |

#### `range-date-anniversary`

Matches date anniversaries independent of year (e.g. "happened in the next 7 days of the year", "falls between Jan 25 and Dec 01"). Only available on DATE fields backed by the anniversary index (event `created-at` and custom date parameters that opt in).

```json
// Relative mode — N days before/after today's anniversary
{
  "values": {
    "mode": "relative",
    "lowerOffsetDays": 0,
    "upperOffsetDays": 7
  }
}

// Absolute mode — month-day range (format MMDD)
{
  "values": {
    "mode": "absolute",
    "lowerDate": "0125",
    "upperDate": "1201"
  }
}
```

| Field             | Type                         | Required when     | Notes                                               |
| ----------------- | ---------------------------- | ----------------- | --------------------------------------------------- |
| `mode`            | `"relative"` \| `"absolute"` | always            | Default `"relative"`.                               |
| `lowerOffsetDays` | int                          | `mode = relative` | Signed. Negative = before, positive = after. `gte`. |
| `upperOffsetDays` | int                          | `mode = relative` | Signed. Exclusive upper (`lt`). Default `1`.        |
| `lowerDate`       | string (`MMDD`)              | `mode = absolute` | e.g. `"0125"` = Jan 25.                             |
| `upperDate`       | string (`MMDD`)              | `mode = absolute` | Inverted ranges wrap around the year boundary.      |

#### `range-date-dayversary`

Matches a weekday + hour-of-day window. Only valid on `created-at`. Use it for recurring weekly schedules — e.g. "orders on Friday evenings".

```json
{
  "type": "event_condition",
  "key": "created-at",
  "operator": "range-date-dayversary",
  "values": { "lowerDay": "518", "upperDay": "523" }
}
```

| Field      | Type   | Format                                                                           | Example                        |
| ---------- | ------ | -------------------------------------------------------------------------------- | ------------------------------ |
| `lowerDay` | string | `DHH` — 1 digit weekday (`1` = Monday … `7` = Sunday) + 2 digit hour (`00`–`23`) | `"518"` = Friday 18:00, `gte`. |
| `upperDay` | string | same                                                                             | `"523"` = Friday 23:00, `lte`. |

If the lower day/hour is *greater* than the upper (e.g. Friday → Monday), the range wraps across the week boundary automatically.

#### `range-date-timeversary`

Matches a time-of-day window independent of the date.

```json
// Relative mode — N minutes around the current time
{
  "values": {
    "mode": "relative",
    "lowerOffsetMinutes": -15,
    "upperOffsetMinutes": 15
  }
}

// Absolute mode — HH:MM range (format HHMM or HH:MM — colons are stripped)
{
  "values": {
    "mode": "absolute",
    "lowerTime": "0900",
    "upperTime": "1800"
  }
}
```

| Field                | Type                         | Required when     | Notes                                 |
| -------------------- | ---------------------------- | ----------------- | ------------------------------------- |
| `mode`               | `"relative"` \| `"absolute"` | always            | Default `"relative"`.                 |
| `lowerOffsetMinutes` | int                          | `mode = relative` | Signed, `gte`.                        |
| `upperOffsetMinutes` | int                          | `mode = relative` | Signed, `lt`. Default `1`.            |
| `lowerTime`          | string (`HHMM`)              | `mode = absolute` | 24h. `"0900"` = 09:00.                |
| `upperTime`          | string (`HHMM`)              | `mode = absolute` | Inverted ranges wrap across midnight. |

### Geographic

#### `geopoint-distance`

Matches events within a radius of a `(longitude, latitude)` point. Only applies to geopoint fields (custom event parameters configured as geopoint).

```json
{
  "type": "event_condition",
  "key": "store_visit.location",
  "operator": "geopoint-distance",
  "values": {
    "longitude": -3.7038,
    "latitude": 40.4168,
    "distance": 25
  }
}
```

| Field       | Type    | Required | Notes                               |
| ----------- | ------- | -------- | ----------------------------------- |
| `longitude` | decimal | yes      | WGS-84 longitude.                   |
| `latitude`  | decimal | yes      | WGS-84 latitude.                    |
| `distance`  | decimal | yes      | Radius. Default unit is kilometres. |

All three fields are required; missing any raises a validation error.

### `values` shape — quick reference

| Operator                                                                       | `values` shape                                                                                                                 |   |     |
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | - | --- |
| `match-all`, `match-none`                                                      | ignored                                                                                                                        |   |     |
| `exists`, `exists-not`                                                         | `[]`                                                                                                                           |   |     |
| `matches-bool`                                                                 | `[true]` or `[false]`                                                                                                          |   |     |
| `contains(-not)`, `startswith(-not)`, `endswith(-not)`, `matches-string(-not)` | `string[]`, optional leading `"&&"` / \`"                                                                                      |   | "\` |
| `matches-number(-not)`                                                         | `number[]`, optional leading `"&&"` / \`"                                                                                      |   | "\` |
| `range-number(-not)`                                                           | `{ lowerNumber, upperNumber, lowerExcludeEquals?, upperExcludeEquals? }`                                                       |   |     |
| `matches-date`                                                                 | `{ date: "YYYY-MM-DD" }`                                                                                                       |   |     |
| `range-date(-not)`                                                             | `{ lowerDate, upperDate, lowerExcludeEquals?, upperExcludeEquals?, lowerRounding?, upperRounding? }`                           |   |     |
| `range-date-relative(-not)`                                                    | `{ lowerOffset, upperOffset, lowerOffsetPeriod, upperOffsetPeriod, lower/upperExcludeEquals?, lower/upperRounding? }`          |   |     |
| `range-date-anniversary`                                                       | `{ mode: "relative", lowerOffsetDays, upperOffsetDays }` or `{ mode: "absolute", lowerDate: "MMDD", upperDate: "MMDD" }`       |   |     |
| `range-date-dayversary`                                                        | `{ lowerDay: "DHH", upperDay: "DHH" }` (only on `created-at`)                                                                  |   |     |
| `range-date-timeversary`                                                       | `{ mode: "relative", lowerOffsetMinutes, upperOffsetMinutes }` or `{ mode: "absolute", lowerTime: "HHMM", upperTime: "HHMM" }` |   |     |
| `geopoint-distance`                                                            | `{ longitude, latitude, distance }`                                                                                            |   |     |

## Date histogram parameters

| Parameter           | Type    | Description                        | Example values                                      |
| ------------------- | ------- | ---------------------------------- | --------------------------------------------------- |
| `field`             | string  | Date field to aggregate on.        | `@created-at`, `@ecommerce_order_create.created-at` |
| `calendar_interval` | string  | Calendar-aware intervals.          | `1d`, `1w`, `1M`, `1y`                              |
| `fixed_interval`    | string  | Fixed time intervals.              | `1h`, `6h`, `30m`, `1d`                             |
| `format`            | string  | Date format for bucket keys.       | `yyyy-MM-dd`, `HH:00`, `E`                          |
| `time_zone`         | string  | Timezone for date calculations.    | `UTC`, `Europe/Madrid`, `America/New_York`          |
| `offset`            | string  | Time offset for bucket boundaries. | `+1h`, `-30m`                                       |
| `min_doc_count`     | integer | Minimum document count per bucket. | `0`, `1`                                            |
| `extended_bounds`   | object  | Extend bounds beyond data range.   | `{"min":"2024-01-01","max":"2024-12-31"}`           |

## Terms aggregation parameters

| Parameter       | Type         | Description                              | Example values                                         |
| --------------- | ------------ | ---------------------------------------- | ------------------------------------------------------ |
| `field`         | string       | Field to group by.                       | `@product-name`, `@event-type`, `@audience-categories` |
| `size`          | integer      | Number of top terms to return.           | `5`, `10`, `100`                                       |
| `missing`       | string       | Label for documents with missing values. | `"unknown"`, `"N/A"`                                   |
| `order`         | object       | Sort order for terms.                    | `{"salesCount":"desc"}`, `{"_count":"asc"}`            |
| `min_doc_count` | integer      | Minimum document count per term.         | `1`, `5`                                               |
| `include`       | array/string | Include only specific terms.             | `["product1","product2"]`                              |
| `exclude`       | array/string | Exclude specific terms.                  | `["excluded1","excluded2"]`                            |

## What's next

- **[Audience query filter](/product-api/audience-query-filter)** — the contact-side filter, with segment and group\_event conditions.
- **[API Reference](/product-api/reference)** — endpoints that accept this filter (`/event/search`, `/event/scroll`, `/event/aggregations`).
