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

# Filtering & concepts

> How the v2 report endpoints work: the shared request shape, the filter tree, grouping, scope, pagination, and streaming.

All five v2 report endpoints share **one request and response shape**:
[Visibility](/rest-api/reports/query-visibility-v2),
[Citations](/rest-api/reports/query-citations-v2),
[Sentiment](/rest-api/reports/query-sentiment-v2),
[Query Fanouts](/rest-api/reports/query-fanouts-v2), and
[Answers](/rest-api/reports/query-answers-v2). Learn it once and every report
works the same way.

<Note>
  All v2 endpoints accept **names or UUIDs** anywhere a filter takes a value.
  Get UUIDs from `GET /v1/org/models`, `/v1/org/regions`, `/v1/org/personas`,
  `/v1/org/assets`, `/v1/org/categories`, and the per-category
  `…/topics`, `…/tags`, `…/prompts` endpoints.
</Note>

## Common request fields

| Field                     | Type                     | Notes                                                                                |
| ------------------------- | ------------------------ | ------------------------------------------------------------------------------------ |
| `category_id`             | UUID (required)          | The category to query.                                                               |
| `start_date` / `end_date` | date (required)          | `YYYY-MM-DD`, Eastern Time, **inclusive** on both ends.                              |
| `scope`                   | `owned` · `all`          | Restrict to your owned assets/domains, or rank everything. Defaults vary per report. |
| `group_by`                | string\[]                | Break results into rows by dimension (see [Grouping](#grouping)).                    |
| `metrics`                 | string\[]                | Which metrics to compute. Returned as **named fields on each row**.                  |
| `interval`                | `day` · `week` · `month` | Bucket size when grouping by `date`. Default `day`.                                  |
| `filter`                  | tree                     | `and`/`or`/`not`/leaf tree (see [Filters](#filters)).                                |
| `sort`                    | `{ field, dir }`         | Order rows (where supported).                                                        |
| `limit`                   | `1`–`50`                 | Rows per page. Default `10`.                                                         |
| `cursor`                  | string                   | Page token from `info.next_cursor`.                                                  |

<Warning>
  Unlike the v1 reports (where `end_date` is exclusive), **v2 `end_date` is
  inclusive**. To get June 9–15, send `start_date: "2026-06-09"`,
  `end_date: "2026-06-15"`.
</Warning>

## Grouping

`group_by` turns one aggregate row into one row per value. Each grouped field
is echoed back on the row:

```json theme={null}
// group_by: ["model"]  →  each row carries the model it's for
{ "asset": { "name": "Profound", "owned": true }, "model": { "id": "…", "name": "ChatGPT" }, "visibility_score": 0.52 }
```

* Group by `date` (with `interval`) for a time series.
* Rows carry a `rank` when grouped by a **non-date** dimension.
* Available dimensions differ per report; see each endpoint's reference.

## Metrics

Request the metrics you want; they come back as **named fields** on each row
(no positional arrays, no `info.query` lookup):

```json theme={null}
{ "rank": 1, "visibility_score": 0.48, "share_of_voice": 0.077, "average_position": 2.5 }
```

## Scope and asset selection

* **`scope`:** `owned` limits to assets/domains you own; `all` ranks across everything.
* **Visibility only:** pick the asset(s) with the separate **`assets`** param: a
  name (`is`), a list (`in`, which overrides `scope`), or `{ op, value }`. A
  selection returns all matches and ignores `limit`.
* **Sentiment** requires an **`asset`** (sentiment is per-brand).

## Filters

`filter` is a recursive tree, **max depth 3**, supported by all five reports:

```json theme={null}
{
  "and": [
    { "or":  [ { "field": "model", "op": "is", "value": "ChatGPT" },
               { "field": "model", "op": "is", "value": "Perplexity" } ] },
    { "not": { "field": "region", "op": "is", "value": "United States" } }
  ]
}
```

Node types: `{ "and": [ … ] }`, `{ "or": [ … ] }`, `{ "not": <node> }`, and
leaves `{ "field", "op", "value" }`.

### Operators

| Operator                                                      | Meaning                                                             |
| ------------------------------------------------------------- | ------------------------------------------------------------------- |
| `is` / `not_is`                                               | Exact match / negated                                               |
| `in` / `not_in`                                               | Match any value in a list (non-empty) / negated                     |
| `contains` / `not_contains`                                   | Substring (case-sensitive)                                          |
| `contains_case_insensitive` / `not_contains_case_insensitive` | Substring (case-insensitive)                                        |
| `matches`                                                     | Regex (pattern ≥ 3 chars)                                           |
| `exists`                                                      | Has any value; only on `tag` / `persona` (wrap in `not` for "none") |

`value` is a single value, or a list for `in` / `not_in`. Names **or** UUIDs;
`contains` / `matches` match on names.

### Two filter layers

Fields fall into two layers. They combine with `and`; **`or`/`not` can't mix
layers** (doing so returns `422`).

**Prompt layer** (full tree, full operator set):
`model`, `topic`, `region`, `persona`, `prompt`, `tag`.

**Entity / citation layer** (top-level `and` leaves only, varies per report):

| Report        | Entity-layer fields                                                                                                                                                                                                                       |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Visibility    | The entity (`asset`) uses the **`assets`** param, *not* `filter`.                                                                                                                                                                         |
| Citations     | `domain` (full ops, subdomain-aware), `page` (full ops), `analysis_type` (`visibility`·`sentiment`·`factcheck`·`all`), `citation_category` (`owned`·`competition`·`social`·`earned_media`·`earned_institutions`·`pr_wire`·`other`·custom) |
| Sentiment     | `theme` / `claim`: `is`/`in`, single value, name or id                                                                                                                                                                                    |
| Query Fanouts | `analysis_type` (`visibility`·`sentiment`·`factcheck`·`all`), `is`/`in`                                                                                                                                                                   |
| Answers       | `analysis_type` is **prompt-level** (`visibility`·`sentiment`·`factcheck`; `is`/`in`/`not_in`; omit = all). `domain`/`page` are top-level `and` leaves: `is` one value or `in` a list (exact cited-URL match)                             |

<Note>
  For citations, filter `domain` in the domains report and `page` when you
  `group_by: ["page"]`; each filters its own report's entity.
</Note>

## Sorting

Where supported, `sort` is `{ "field": "<metric>", "dir": "asc" | "desc" }`.
The field must be a requested, sortable metric (or `date` when grouped by
date). **Citations has no `sort`:** it's always ranked most-cited first.

## Pagination

Responses return `limit` rows plus `info.next_cursor`. Pass that token back as
`cursor` to get the next page; `next_cursor` is `null` on the last page.

## Streaming

Every endpoint has a **`/stream`** variant (Server-Sent Events): a `summary`
event (the `info` block), then one `result` event per row. `limit`/`cursor`
are ignored; it returns everything by default. Pass `max_results` to cap.

## Response shape

Every report returns `{ info, data }`:

```json theme={null}
{
  "info": {
    "total_results": 8427,
    "count": 10,
    "next_cursor": "…",
    "models": ["ChatGPT", "Google Gemini", "..."],
    "start_date": "2026-06-09",
    "end_date": "2026-06-15",
    "filter": null
  },
  "data": [
    { "rank": 1, "visibility_score": 0.48 }
  ]
}
```

`info` echoes the resolved query (models in scope, the applied filter, dates,
pagination); `data` is the rows, with metrics as named fields and any
`group_by` dimensions attached.
