# API filters and date periods

> One filter syntax for the dashboard, the API, MCP and exports. Learn the period, comparison and segment inputs and how to combine conditions with AND and OR.

Every stats operation accepts the same inputs:

| Parameter | Description |
|---|---|
| `period` | A preset from [Periods](#periods). Default `30d` |
| `from`, `to` | Dates (`YYYY-MM-DD`, inclusive) in the site's timezone, with `period=custom`. Without `from`, the range starts 29 days before `to`. Without `to`, it ends today |
| `compare` | `none` (default), `previous_period`, `previous_year`, `custom` ([Comparisons](#comparisons)) |
| `compare_from`, `compare_to` | Dates (inclusive) with `compare=custom`. Both are needed, or there is no comparison |
| `match_weekday` | `true` to align the comparison by day of the week. Default `false` |
| `filters` | See [Syntax](#syntax) |
| `segment_id` | Apply a saved segment (`seg_…`). Its conditions are added to `filters` with AND. A segment you can't use (another member's private one) or an unknown id is ignored |
| `include_imported` | `true` to add imported history (for example from Google Analytics) to the numbers. Default `false`. It only applies when there are no filters and no segment |

## Periods

All periods are whole days in the site's timezone, except `24h`.

| `period` | Range |
|---|---|
| `today` | Today so far |
| `yesterday` | Yesterday |
| `24h` | The last 24 hours, up to now |
| `7d`, `14d`, `28d`, `30d`, `90d` | That many days, ending with today |
| `mtd`, `qtd`, `ytd` | From the start of this month, quarter or year to today |
| `12mo` | The last 12 months, ending with today |
| `last_month`, `last_quarter`, `last_year` | The previous calendar month, quarter or year |
| `all` | From the site's first event (or first imported day) to today |
| `custom` | `from` to `to` |

## Comparisons

| `compare` | Compared with |
|---|---|
| `none` | Nothing |
| `previous_period` | The same length of time right before the period. With `match_weekday`, shifted back by whole weeks instead |
| `previous_year` | The same dates one year earlier. With `match_weekday`, 52 weeks earlier |
| `custom` | `compare_from` to `compare_to` |

## Syntax

`filters` is a JSON array. Each item is a condition
`[dimension, operator, value]`. Items are combined with **AND**. Wrap
conditions in `{"any": [...]}` to combine them with **OR**.

```json
[
  ["page", "starts_with", "/blog"],
  ["country", "any_of", ["DE", "FR", "NL"]],
  { "any": [["channel", "is", "email"], ["utm_medium", "is", "newsletter"]] }
]
```

In URLs, send it URL-encoded:

```sh
curl -G "https://privatusanalytics.com/sites/pa_7Q2K9XH3AB/stats/aggregate.json" \
  --data-urlencode 'filters=[["page","starts_with","/blog"],["country","any_of",["DE","FR"]]]' \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
```

In JSON bodies and MCP tool calls, pass the array itself. Conditions can
also be objects: `{"dimension": "page", "operator": "is", "value": "/"}`.

Limits: 20 conditions in total, regular expressions up to 200 characters.
An `any` group needs at least one condition.

## Operators

| Operator | Value | Meaning |
|---|---|---|
| `is`, `is_not` | a string | Equals / doesn't equal (case-sensitive) |
| `any_of`, `none_of` | a list with at least one item | Equals one of / none of (case-sensitive) |
| `contains`, `not_contains` | a string | Contains / doesn't contain the text, ignoring case |
| `starts_with` | a string | Starts with the text (case-sensitive) |
| `regex`, `not_regex` | a string | Matches / doesn't match a PostgreSQL regular expression (case-sensitive) |
| `gt`, `lt` | a number | Greater / less than |
| `between` | `[min, max]`, two numbers | Inclusive range |

`gt`, `lt` and `between` compare numbers, so they are meant for numeric
properties (`prop:<key>`). A stored value that isn't a number never
matches.

## Dimensions

The same keys are used for filters and for
`/sites/{site_id}/breakdown/{dimension}`.

| Dimension | Label | Scope |
|---|---|---|
| `channel` | Channel | Visit (first touch) |
| `referrer` | Referrer | Visit (first touch) |
| `referrer_url` | Referrer URL | Visit (first touch) |
| `utm_source` | UTM source | Visit (first touch) |
| `utm_medium` | UTM medium | Visit (first touch) |
| `utm_campaign` | UTM campaign | Visit (first touch) |
| `utm_content` | UTM content | Visit (first touch) |
| `utm_term` | UTM term | Visit (first touch) |
| `ref` | Ref | Visit (first touch) |
| `hostname` | Hostname | Visit (first touch) |
| `country` | Country | Visit (first touch) |
| `language` | Language | Visit (first touch) |
| `device` | Device | Visit (first touch) |
| `browser` | Browser | Visit (first touch) |
| `browser_version` | Browser version | Visit (first touch) |
| `os` | Operating system | Visit (first touch) |
| `os_version` | OS version | Visit (first touch) |
| `screen` | Screen size | Visit (first touch) |
| `entry_page` | Entry page | Visit only |
| `exit_page` | Exit page | Visit only |
| `interaction` | Interaction | Visit only |
| `page` | Page | Event / page |
| `event` | Event | Event / page |
| `goal` | Goal | Goal |
| `prop:<key>` | Custom property value, e.g. `prop:plan` | Event |

- **Visit (first touch)** dimensions describe how a visit started. Events
  carry their visit's values, so "country is DE" works for events too.
- **Visit only**: entry and exit pages, and `interaction`: whether the
  tracker saw a real input (a pointer, key, touch or wheel event) during
  the visit. Its values are `seen`, `none` and `unknown` (the visit can't
  tell, for example server-side or pixel hits).
- **Event / page**: `page` filters visits that viewed a page, and `event`
  filters visits in which an event happened.
- **Goal**: `["goal", "is", "goal_…"]` filters visits that converted. The
  value is the goal's id. Use `is` or `any_of` for visits that converted,
  and `is_not` or `none_of` for visits that didn't. An unknown or archived
  goal id matches nothing.
- **Properties:** `prop:<key>`, e.g. `["prop:plan", "is", "pro"]`, or
  `["prop:seats", "gt", 5]`. The key is 1 to 64 lowercase letters, digits
  or underscores.

Channel values are the keys listed in [Channels](/docs/metrics/channels)
(`organic_search`, `ai_assistants`…). Countries are ISO 3166-1 alpha-2
codes. There are no region or city dimensions.

## Errors

An unknown dimension or operator, a list operator without a list, a
number operator without a number, an invalid regex, or `filters` that
isn't a JSON array returns `422 validation_failed` with the reason in
`message`. An unknown `period`, `compare` or `interval` value returns
`422` too.
