> ## 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

The 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),
[Answers](/rest-api/reports/query-answers-v2), and
[FactCheck](/rest-api/reports/query-factcheck-v2) (accuracy scores +
[claims](/rest-api/reports/query-factcheck-claims-v2)). Learn it once and every
report works the same way. YouTube social reports are also v2 reports, with
channel and video-specific grouping and attribution fields; see
[YouTube Channels](/rest-api/reports/query-youtube-channels-v2) and
[YouTube Videos](/rest-api/reports/query-youtube-videos-v2) for their
endpoint-specific request and response fields.

<Note>
  FactCheck uses the same `{ info, data }` envelope but is **per-category** (no
  `scope`/`assets`/`metrics` params) and takes a **narrower `filter`**: a
  top-level `and` of single-field leaves, only `topic` negatable. See its pages
  for specifics.
</Note>

<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**, for every report, including
YouTube social 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), `citation_tag` (your custom tags; `is`/`in`) |
| Sentiment | `theme` / `claim`: `is`/`in`, single value, name or id. With `source: "citation"`: `citation_category` (`is`/`in`) and `page` (`contains_case_insensitive`, one value) |
| 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) |
| YouTube social | `channel` is the entity-layer filter. `domain` and `page` do not apply because YouTube rows are already scoped to YouTube sources. |

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

<Note>
  Sentiment with `source: "citation"` always returns page rows and does not
  support `group_by`. Use prompt-layer filters to narrow which citations count,
  and use `citation_category` or `page` to narrow the returned pages.
</Note>

<Note>
  YouTube reports use the `channel` entity filter. It can be combined with
  prompt-level filters using `and`, but not with them under `or` or `not`.
  YouTube `domain` and `page` filters are rejected rather than approximated.
</Note>

<Note>
  List a category's citation tags with [Get Citation Tags](/api-reference/organization/get-category-citation-tags).
  Put every tag you want in a single `in` leaf (values are OR'd). AND-ing two
  separate `citation_tag` leaves, or passing an empty list, returns `422`.
</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 report endpoint except YouTube social reports 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. Sentiment streaming accepts both `source: "response"`
and `source: "citation"` using the same source-specific row shapes as the
paginated endpoint.

## 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.
