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

> The sources entity - the Source Analysis overview, tables, gap views and the dashboard source widgets.

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

One row per cited domain, hostname, URL, category or page type, or per platform, country, topic, tag, prompt or time bucket. No `dimensions` gives one row of totals. Read the [Reports API](/docs/reports-api) page first for the request shape, filter roles and the compare window.

## Dimensions

| id | Row | Notes |
| - | - | - |
| `domain` | Per cited domain, `reddit.com` | |
| `hostname` | Per cited host, `www.reddit.com` | |
| `url` | Per cited page | |
| `category` | Per domain category | `news`, `blog`, `official`, … from `GET /api/v1/domain-categories` |
| `page_type` | Per page type | `homepage`, `product_page`, `listicle`, `comparison`, `discussion`, … |
| `page_sentiment` | What the page says about the lens brand | `positive`, `neutral`, `negative`, or `null` when the brand is not on the page or the page is not analysed |
| `brand_on_page` | The page's relation to the lens brand | `mentioned`, `gap` (only other tracked brands are named), `none` (analysed, no tracked brand), `not_analysed` |
| `service`, `country`, `tag`, `topic`, `prompt`, `date`, `week`, `month` | As on visibility | |

`page_sentiment` and `brand_on_page` come from Surfacd's page analysis of the cited URLs. They describe the page against one brand, the **lens brand**: the `page_brand` filter, or your primary brand when there is none.

## Measures

| id | Meaning | Unit |
| - | - | - |
| `citations` | Times a page in the group was cited | count |
| `responses` | Answers that cite the group | count |
| `answers` | Answers in the population, the denominator of `citation_rate` | count |
| `pages` | Distinct pages cited in the group | count |
| `domains` | Distinct domains cited in the group | count |
| `citations_per_response` | `citations` ÷ answers in the population | number |
| `analysed_pages` | Cited pages the page analysis has processed for this project. All of the row's analysed pages, even when a `brand_on_page`, `page_sentiment` or `competitors_on_page` filter narrows the citations. | count |
| `mentioning_pages` | Analysed pages that name the lens brand, counted the same way. The table's "Mentioned? 6/12" is `mentioning_pages` / `analysed_pages`. | count |
| `citation_rate` | Share of answers that cite the group | decimal, `percent` |
| `avg_rank` | Average citation position. Lower is better. | number |
| `share` | The group's share of all citations in the population | decimal, `percent` |
| `rank` | Position by citations within the group (1 = most cited). Assigned before row filters. | integer |

## Filters

* **Population** fields as on the main page. The population is the same set of answers the Sources page uses.
* **Entity** fields `domain`, `hostname`, `url`, `category`, `page_type`, `page_sentiment` and `brand_on_page` take `in`, `not_in` and `contains` on the label when their dimension is on the row. They select rows after ranking.
* **Page** filters narrow which cited pages count before grouping and ranking, without changing the population. `competitors_on_page` (the number of other tracked brands named on the page) takes `in` or a comparison. `page_type`, `category`, `brand_on_page` and `page_sentiment` act as page filters when they are **not** on the row: `page_type in ["listicle", "comparison"]` keeps one row per domain that counts only those pages, which is what the page's multi-select shows, and `brand_on_page in ["gap"]` is the Sources page's gaps toggle.
* `analysed_pages`, `mentioning_pages` and the `brands_on_page` block describe all of a row's analysed pages: a `brand_on_page`, `page_sentiment` or `competitors_on_page` page filter does not narrow them, as on the Sources page. So with such a filter they cannot be used in `order_by`, a filter or `top`, nor with a compare window and a time dimension.
* **Lens**: `page_brand` takes `in` with one brand UUID and removes no rows. It names the brand that `page_sentiment`, `brand_on_page`, `competitors_on_page`, `mentioning_pages` and the `brands_on_page` block describe.

## Related block

`"related": ["brands_on_page"]` adds, per row, the tracked brands named on its analysed pages with the number of pages each appears on. The lens brand comes first, then most pages first. This is what draws the brand logos in the Sources table.

```json theme={null}
"related": {
  "brands_on_page": [
    { "row": 0, "brands": [
      { "uuid": "…", "name": "Apple", "domain": "apple.com", "pages": 6 },
      { "uuid": "…", "name": "Logitech", "domain": "logitech.com", "pages": 6 }
    ] }
  ]
}
```

## Dashboard

### Citations KPI card

```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",
  "measures": ["citations"]
}
```

### Source categories donut

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["category"],
  "measures": ["citations", "share"]
}
```

### Top sources

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["domain"],
  "measures": ["citations", "responses", "share", "rank"],
  "limit": 5
}
```

## Sources overview

### KPIs

Citations, answers citing, pages, domains and citations per answer, with trends.

```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",
  "measures": ["citations", "responses", "pages", "domains", "citations_per_response"]
}
```

### Domain share over time

The top five domains by week. `rank` is per week, so the filter keeps each week's top five.

```json theme={null}
{
  "start_date": "2026-07-06",
  "end_date": "2026-09-28",
  "dimensions": ["domain", "week"],
  "measures": ["share", "citations"],
  "filters": [{ "field": "rank", "op": "lte", "value": 5 }],
  "order_by": [{ "field": "week", "dir": "asc" }, { "field": "share", "dir": "desc" }],
  "limit": 1000
}
```

### Domain movers

```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": ["domain"],
  "measures": ["share", "citations"],
  "order_by": [{ "field": "share_change", "dir": "desc" }],
  "limit": 10
}
```

### Page type donut

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["page_type"],
  "measures": ["citations", "share"]
}
```

### Gap ratio KPI

How many cited pages name your brand, name only competitors, or name nobody you track.

```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_on_page"],
  "measures": ["pages", "citations"]
}
```

## Sources tables

### Domains table with "Mentioned?" and brand logos

The [Source Analysis](/docs/source-analysis) domains table. `mentioning_pages` / `analysed_pages` is the "Mentioned?" column; `brands_on_page` draws the logos.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["domain"],
  "measures": ["citations", "responses", "citation_rate", "avg_rank", "share", "rank", "analysed_pages", "mentioning_pages"],
  "related": ["brands_on_page"],
  "limit": 25
}
```

Add `{ "field": "page_brand", "op": "in", "values": ["COMPETITOR_UUID"] }` to see the same table through a competitor's eyes. Add a compare window for the previous-period deltas. Use `offset` for the next page.

### Hostnames and URLs tables

Replace `domain` with `hostname` or `url`:

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["url"],
  "measures": ["citations", "responses", "avg_rank", "rank"],
  "limit": 25
}
```

### Page type and category filters

The multi-select above the table. Keep `domain` as the only dimension and filter `page_type`: one row per domain counting only its listicle and comparison pages.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["domain"],
  "measures": ["citations", "pages", "responses", "share"],
  "filters": [{ "field": "page_type", "op": "in", "values": ["listicle", "comparison"] }],
  "limit": 25
}
```

### Gaps only

Domains ranked by their pages that name at least one competitor and not you. Off the row, `brand_on_page` is a page filter, so the domains are ranked by their gap pages alone; `competitors_on_page` is the "minimum competitors" control.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["domain"],
  "measures": ["citations", "responses", "pages"],
  "filters": [
    { "field": "brand_on_page", "op": "in", "values": ["gap"] },
    { "field": "competitors_on_page", "op": "gte", "value": 1 }
  ],
  "related": ["brands_on_page"],
  "limit": 25
}
```

### Negative pages

The sentiment filter: domains ranked by their analysed pages that speak negatively about the lens brand.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["domain"],
  "measures": ["citations", "pages"],
  "filters": [{ "field": "page_sentiment", "op": "in", "values": ["negative"] }]
}
```

### Search

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["url"],
  "measures": ["citations", "rank"],
  "filters": [{ "field": "url", "op": "contains", "value": "reddit" }]
}
```

### Prompts citing a page

Open a URL and see which prompts cite it.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["url", "prompt"],
  "measures": ["citations", "responses"],
  "filters": [{ "field": "url", "op": "in", "values": ["https://www.reddit.com/r/headphones/comments/…"] }],
  "limit": 50
}
```

## More examples

### Domain share by platform

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["domain", "service"],
  "measures": ["share"],
  "order_by": [{ "field": "service", "dir": "asc" }, { "field": "share", "dir": "desc" }]
}
```

### What the platforms read when they are negative about you

`mentions_brand` plus `mention_sentiment` make "answers negative about us" the population. `answers` prints how many that is.

```json theme={null}
{
  "start_date": "2026-09-01",
  "end_date": "2026-09-28",
  "dimensions": ["domain"],
  "measures": ["citations", "responses", "answers", "share"],
  "filters": [
    { "field": "mentions_brand", "op": "in", "values": ["BRAND_UUID"] },
    { "field": "mention_sentiment", "op": "in", "values": ["negative"] }
  ],
  "limit": 25
}
```


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