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

# Reports API: Visibility

> The visibility entity - the Dashboard, Rankings, Analyse, Prompts and Brand Mentions reports.

`POST /api/v1/projects/{project}/reports/visibility`

How brands appear in AI answers, at two grains:

* **Totals.** One row per brand, or per brand per platform, country, topic, tag, prompt or time bucket. Leave `brand` out of `dimensions` and the rows aggregate across all tracked brands.
* **Answer rows.** Add `answer` to `dimensions` for one row per AI answer, newest first. This is the Brand Mentions list. See [Answer rows](#answer-rows).

Read the [Reports API](/docs/reports-api) page first for the request shape, filter roles and the compare window.

## Dimensions

| id | Row | Label columns |
| - | - | - |
| `brand` | One row per tracked brand, zero-filled when it has no mentions | `brand`, `brand_uuid` |
| `answer` | One row per AI answer. See [Answer rows](#answer-rows). | `answer_uuid`, `answered_at` |
| `position` | One row per position the brand was named at (1 = first). Use with `brand`. | `position` |
| `mention_sentiment` | One row per sentiment the brand was named with. Needs `brand`. | `positive`, `neutral`, `negative` |
| `service` | Per AI platform | `service` |
| `country` | Per country | `country` |
| `tag` | Per tag. A prompt with two tags counts in both rows, so tag rows do not sum. | `tag`, `tag_uuid` |
| `topic` | Per topic. Prompts without a topic form one `null` group. | `topic`, `topic_uuid` |
| `prompt` | Per prompt | `prompt`, `prompt_uuid` |
| `date`, `week`, `month` | Per day, week (Monday start) or month. One per query. | `date`, `week` or `month` |

## Measures

| id | Meaning | Unit |
| - | - | - |
| `visibility` | Share of answers in the group that name the brand. Without `brand` on the row: the mean brand visibility of the group. | decimal, `percent` |
| `coverage` | Share of answers that name any tracked brand. Useful on rows without `brand`. | decimal, `percent` |
| `mentions` | Times the brand is named | count |
| `answers` | Answers in the group's population, the denominator of `visibility` | count |
| `avg_position` | Average position the brand is named at. Lower is better. | number |
| `sentiment` | Net Sentiment Index of the brand's mentions | 0 to 100 |
| `rank` | Position of the brand by visibility within its group (1 = best). Assigned before row filters. | integer |
| `citations` | Pages the answer cites. Needs `answer` in `dimensions`. | count |

## Filters

* Population fields as on the main page. `service in ["chatgpt"]` changes the denominator; `brand in [...]` does not.
* `brand` is an entity field: `in` and `not_in` with brand UUIDs, `contains` with text matched on the name. It needs `brand` in `dimensions`.
* `mention_sentiment` is an entity field here, not a population filter. It needs `mention_sentiment` in `dimensions` and keeps rows without changing the denominator.
* Any measure takes a comparison: `mentions gte 5`, `visibility lt 0.1`, `rank lte 3`, `visibility_change lte -0.05`.

## Answer rows

Add `answer` to `dimensions` and the report becomes a list: one row per AI answer in the population, newest first. The body, filters, ordering and paging work as for totals. Some fields have no value on one answer, so the API rejects them with `answer` on the row:

* the compare window,
* the `visibility`, `coverage`, `answers` and `rank` measures,
* the `position` dimension.

`citations`, `search`, `answered_at` and the related blocks below work only with `answer` on the row.

### Row

With `answer` in `dimensions`, every row carries these columns. Put `answer` first in `dimensions` to get them first.

| Column | Meaning |
| - | - |
| `answer_uuid` | The answer's id |
| `answered_at` | When the answer was collected, `2026-09-24 10:08:03` |

### Other dimensions

| id | Effect on the rows | Label columns |
| - | - | - |
| `brand` | One row per tracked brand the answer names. An answer that names no tracked brand has no row; a brand that is not named has no row. | `brand`, `brand_uuid` |
| `mention_sentiment` | How the answer speaks of the row brand. Needs `brand`. | `positive`, `neutral`, `negative` |
| `service`, `country`, `tag`, `topic`, `prompt`, `date`, `week`, `month` | Add columns; the grain stays the answer | as above |

### Measures

| id | Meaning | Unit |
| - | - | - |
| `mentions` | Tracked brands the answer names; 1 on a brand row | count |
| `avg_position` | Where the row brand is named. `null` without `brand`. Lower is better. | integer |
| `sentiment` | Net Sentiment Index of the answer's mentions, or of the row brand's mention | 0 to 100 |
| `citations` | Pages the answer cites | count |

### Filters

* Population fields as on the main page. To keep only answers negative about a brand, use the `mention_sentiment` dimension and an entity filter on it.
* `brand` and `mention_sentiment` are entity fields and need their dimension.
* `answer` takes `in` with answer UUIDs, or `contains` with text matched on the answer's full text.
* `search` takes `contains` only and matches the prompt text or a mention snippet. On a brand row, that brand's snippet. This is the Mentions page's search box.
* Measures take comparisons: `avg_position lte 3`, `mentions eq 0`, `citations gte 1`.
* Sort by `answered_at` for newest or oldest first. `answer` and `search` select rows but do not sort.

All text matching is case-insensitive.

### Related blocks

Text stays out of the row so the list stays light. Ask for it per page:

| id | Per row |
| - | - |
| `brands_in_answer` | Every tracked brand the answer names, first named first: `{ "uuid", "name", "domain", "position", "sentiment" }` |
| `sources_in_answer` | The pages it cites: `{ "url", "domain", "rank" }` |
| `answer_text` | The full answer text, as `text` |

```json theme={null}
"related": {
  "brands_in_answer": [
    { "row": 0, "brands": [{ "uuid": "…", "name": "Apple", "domain": "apple.com", "position": 1, "sentiment": "neutral" }] }
  ],
  "sources_in_answer": [
    { "row": 0, "sources": [{ "url": "https://www.wsj.com/…", "domain": "wsj.com", "rank": 1 }] }
  ]
}
```

### Top brands per row

`"related": ["top_brands"]` works on any row that is not an answer: per prompt, topic, platform, country, tag or date. For each returned row it lists the five tracked brands named in most of the answers in the row's group, whatever brand the row itself is for. Each brand has the number of those answers that name it and its average position there. A tie goes to the lower total position, then the name. This is the Prompts page's "top brands" column, for the rows on the page only.

```json theme={null}
"related": {
  "top_brands": [
    { "row": 0, "brands": [{ "uuid": "…", "name": "Apple", "domain": "apple.com", "mentions": 41, "avg_position": 1.6, "rank": 1 }] }
  ]
}
```

In the examples below, `BRAND_UUID` is your primary brand's UUID from `GET /api/v1/projects/{project}/brands` or the [values endpoint](/docs/reports-api#finding-identifiers).

## Dashboard

### KPI cards: visibility, sentiment, average position

One row for your brand with a compare window. The response also fills `benchmarks[0]` with the trend of the first measure. Reorder `measures` to put the card's measure first, or read `visibility_change`, `sentiment_change` and `avg_position_change` from the row.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "compare_start_date": "2026-08-04",
  "compare_end_date": "2026-08-31",
  "dimensions": ["brand"],
  "measures": ["visibility", "sentiment", "avg_position"],
  "filters": [{ "field": "brand", "op": "in", "values": ["BRAND_UUID"] }]
}
```

The citations card lives on the [sources entity](/docs/reports-api-sources#dashboard).

### Visibility chart

Every brand by day. Plot one line per `brand`, x = `date`, y = `visibility`. Swap `date` for `week` or `month` for the chart's period toggle.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["brand", "date"],
  "measures": ["visibility"],
  "order_by": [{ "field": "date", "dir": "asc" }],
  "limit": 1000
}
```

### Industry ranking

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["brand"],
  "measures": ["visibility", "mentions", "avg_position", "sentiment", "rank"]
}
```

### Position distribution

How often your brand is named first, second, third and so on.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["brand", "position"],
  "measures": ["mentions", "visibility"],
  "filters": [{ "field": "brand", "op": "in", "values": ["BRAND_UUID"] }],
  "order_by": [{ "field": "position", "dir": "asc" }]
}
```

### Underperforming prompts

The prompts where your brand shows up least.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["brand", "prompt"],
  "measures": ["visibility", "mentions"],
  "filters": [{ "field": "brand", "op": "in", "values": ["BRAND_UUID"] }],
  "order_by": [{ "field": "visibility", "dir": "asc" }],
  "limit": 10
}
```

Recent mentions are an [answer rows](#recent-mentions) query.

### Recent mentions

The newest answers that name your brand, with sentiment, platform, prompt, position and the other brands in the answer.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["answer", "brand", "mention_sentiment", "service", "prompt"],
  "measures": ["avg_position", "citations"],
  "filters": [{ "field": "brand", "op": "in", "values": ["BRAND_UUID"] }],
  "order_by": [{ "field": "answered_at", "dir": "desc" }],
  "related": ["brands_in_answer"],
  "limit": 10
}
```

## Brand Rankings

### Rankings table with deltas

The [Brand Rankings](/docs/brand-rankings) table over the last seven days against the seven before. `rank_change` is negative when a brand moved up.

```json theme={null}
{
  "start_date": "2026-09-22",
  "end_date": "2026-09-28",
  "compare_start_date": "2026-09-15",
  "compare_end_date": "2026-09-21",
  "dimensions": ["brand"],
  "measures": ["visibility", "mentions", "avg_position", "sentiment", "rank"]
}
```

To show only your starred competitors, add `{ "field": "brand", "op": "in", "values": ["…", "…"] }`. It hides rows and leaves every number and rank as it was.

### Top three per platform

`rank` is assigned within each platform, so a filter on it is "top three on each".

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["brand", "service"],
  "measures": ["visibility", "rank"],
  "filters": [{ "field": "rank", "op": "lte", "value": 3 }],
  "order_by": [{ "field": "service", "dir": "asc" }, { "field": "rank", "dir": "asc" }]
}
```

### Compare visibility over time

The competitor chart: pick the brands with a `brand` filter and plot one line each by week.

```json theme={null}
{
  "start_date": "2026-07-06",
  "end_date": "2026-09-28",
  "dimensions": ["brand", "week"],
  "measures": ["visibility"],
  "filters": [{ "field": "brand", "op": "in", "values": ["BRAND_UUID", "COMPETITOR_UUID"] }],
  "order_by": [{ "field": "week", "dir": "asc" }, { "field": "brand", "dir": "asc" }],
  "limit": 1000
}
```

## Analyse

### The matrix

[Matrix Analysis](/docs/matrix-analysis) is brand × platform with three metrics. Swap `service` for `country`, `topic` or `tag` for the other views. Pivot the rows on your side: one row per cell.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["brand", "service"],
  "measures": ["visibility", "mentions", "avg_position"],
  "order_by": [{ "field": "brand", "dir": "asc" }, { "field": "service", "dir": "asc" }],
  "limit": 1000
}
```

### Visibility change by platform

Which platforms drove the change for your brand. Sort by `visibility_change` ascending to see the drops first.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "compare_start_date": "2026-08-04",
  "compare_end_date": "2026-08-31",
  "dimensions": ["brand", "service"],
  "measures": ["visibility", "mentions"],
  "filters": [{ "field": "brand", "op": "in", "values": ["BRAND_UUID"] }],
  "order_by": [{ "field": "visibility_change", "dir": "asc" }]
}
```

## Prompts

### The prompts table

The [Prompts report](/docs/prompts-report) for your brand: one row per prompt.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["brand", "prompt"],
  "measures": ["visibility", "mentions", "avg_position", "sentiment"],
  "filters": [{ "field": "brand", "op": "in", "values": ["BRAND_UUID"] }],
  "related": ["top_brands"],
  "limit": 1000
}
```

`top_brands` adds each prompt's five most-named brands, the table's "Top brands" column. Add `{ "field": "prompt", "op": "contains", "value": "running" }` for the search box, or a `tag` / `topic` population filter for the sidebar filters.

### Topics rollup

Prompts are detailed, so roll them up. The same request with `topic` gives one row per topic, with the topic's prompts pooled into one denominator. Prompts without a topic are the row where `topic` is `null`.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["brand", "topic"],
  "measures": ["visibility", "mentions", "avg_position", "sentiment"],
  "filters": [{ "field": "brand", "op": "in", "values": ["BRAND_UUID"] }],
  "related": ["top_brands"]
}
```

### Visibility by week

```json theme={null}
{
  "start_date": "2026-07-06",
  "end_date": "2026-09-28",
  "dimensions": ["brand", "week"],
  "measures": ["visibility"],
  "filters": [{ "field": "brand", "op": "in", "values": ["BRAND_UUID"] }],
  "order_by": [{ "field": "week", "dir": "asc" }],
  "limit": 1000
}
```

## Brand Mentions

### The mentions list

The [Brand Mentions](/docs/brand-mentions) page: every answer that names the selected brand, newest first, 25 per page. Use `offset` for the next page and `page.total` for the pager.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["answer", "brand", "mention_sentiment", "service", "prompt"],
  "measures": ["avg_position", "citations"],
  "filters": [{ "field": "brand", "op": "in", "values": ["BRAND_UUID"] }],
  "order_by": [{ "field": "answered_at", "dir": "desc" }],
  "related": ["brands_in_answer", "sources_in_answer"],
  "limit": 25,
  "offset": 0
}
```

The page's filters map directly:

| Filter in the app | Add to `filters` |
| - | - |
| Sentiment | `{ "field": "mention_sentiment", "op": "in", "values": ["negative"] }` |
| Position 1 to 3 | `{ "field": "avg_position", "op": "lte", "value": 3 }` |
| Platform, country, tag, topic | the population filters |
| Search | `{ "field": "search", "op": "contains", "value": "running" }` |

### Search

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["answer", "brand", "prompt"],
  "measures": ["avg_position", "citations"],
  "filters": [
    { "field": "brand", "op": "in", "values": ["BRAND_UUID"] },
    { "field": "search", "op": "contains", "value": "running" }
  ],
  "order_by": [{ "field": "answered_at", "dir": "desc" }]
}
```

To search the answer text itself rather than the prompt and snippet, use `{ "field": "answer", "op": "contains", "value": "battery life" }`.

### One answer in full

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["answer", "service", "prompt"],
  "measures": ["mentions", "citations"],
  "filters": [{ "field": "answer", "op": "in", "values": ["ANSWER_UUID"] }],
  "related": ["answer_text", "brands_in_answer", "sources_in_answer"]
}
```

## More examples

A few queries you cannot run in the app today, but which the same request shape supports.

### A project KPI without brand rows

No `dimensions`: one row for the whole project. `visibility` is the mean brand visibility, `coverage` the share of answers that name any tracked brand.

```json theme={null}
{
  "start_date": "2026-09-22",
  "end_date": "2026-09-28",
  "compare_start_date": "2026-09-15",
  "compare_end_date": "2026-09-21",
  "measures": ["visibility", "coverage"]
}
```

`"dimensions": ["service"]` gives the same per platform; `["prompt"]` with `coverage` shows which prompts name nobody you track.

### Co-mentions: who appears when you appear

`mentions_brand` makes "answers that name us" the population. Every other brand's `visibility` is then its share of those answers. `answers` prints the denominator.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["brand"],
  "measures": ["visibility", "mentions", "answers"],
  "filters": [{ "field": "mentions_brand", "op": "in", "values": ["BRAND_UUID"] }]
}
```

### Your visibility inside the answers that cite a domain

```json theme={null}
{
  "start_date": "2026-07-06",
  "end_date": "2026-09-28",
  "dimensions": ["brand", "week"],
  "measures": ["visibility", "answers"],
  "filters": [
    { "field": "cites_domain", "op": "in", "values": ["reddit.com"] },
    { "field": "brand", "op": "in", "values": ["BRAND_UUID"] }
  ],
  "order_by": [{ "field": "week", "dir": "asc" }]
}
```

### Where a competitor is named and you are not

`mentions_brand` with the competitor, `brand` with you. A row at 0% is a prompt where they appear and you do not.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["brand", "prompt"],
  "measures": ["visibility", "answers"],
  "filters": [
    { "field": "mentions_brand", "op": "in", "values": ["COMPETITOR_UUID"] },
    { "field": "brand", "op": "in", "values": ["BRAND_UUID"] },
    { "field": "visibility", "op": "eq", "value": 0 }
  ]
}
```

### Every answer, with brand and citation counts

No `brand` dimension: one row per answer whether or not it names anyone.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["answer", "service", "prompt"],
  "measures": ["mentions", "citations"],
  "order_by": [{ "field": "answered_at", "dir": "desc" }],
  "limit": 25
}
```

### Answers that name no tracked brand

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["answer", "service", "prompt"],
  "measures": ["mentions", "citations"],
  "filters": [{ "field": "mentions", "op": "eq", "value": 0 }],
  "related": ["answer_text"],
  "limit": 25
}
```

### Answers that name a competitor

`mentions_brand` makes those answers the population. Add `brands_in_answer` to see whether you are in them too.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["answer", "service", "prompt"],
  "measures": ["mentions", "citations"],
  "filters": [{ "field": "mentions_brand", "op": "in", "values": ["COMPETITOR_UUID"] }],
  "related": ["brands_in_answer"],
  "limit": 25
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.