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

# Reports: dimensions, budgets and paging

> Report rows for brands, domains and URLs, broken down by date, engine, segment and prompt, with every figure's band and sample size.

Three report routes turn your collected answers into rows you can load into a warehouse or a spreadsheet:

| Route | One row per | Scope |
| - | - | - |
| `GET /v1/projects/{projectId}/reports/brands` | brand named | `metrics:read` |
| `GET /v1/projects/{projectId}/reports/domains` | domain an engine retrieved or cited | `sources:read` |
| `GET /v1/projects/{projectId}/reports/urls` | URL an engine retrieved or cited | `sources:read` |

A row is one brand, domain or URL for one combination of the dimensions you ask for: one brand on one engine on one day, say. These are not the same as an agency's **monthly reports** (the frozen months a client sees), which have their own routes in the [API reference](https://docs.citino.ai/api-reference).

## What a report counts

A report counts your **unbranded prompts**: the questions that name neither your brand nor a competitor you track, the same set behind Overview's headline figures. By default it covers the segments you have claimed and the engines your project samples. Every response opens with a `scope` object that says exactly what it counted.

The brands report uses the same calculation as the Brands screen's league table: a row with no dimensions is the brand's league row for the window. The domains and URLs reports use the Sources screen's calculation.

## Parameters

**from** `string` *required*

First day, inclusive, as `YYYY-MM-DD`. Days are the project's market days.

**to** `string` *required*

Last day, inclusive. Must not be before `from`.

**dimensions** `string`

Comma-separated, any of `date`, `engine`, `segment` and `prompt`, each at most once. The URLs report has no `prompt` dimension. Leave it out for one row per brand, domain or URL over the whole window.

**interval** `string` (default: day)

`day`, `week` or `month`: how much time one row covers. Only with `date` among the dimensions. A row's `date` is its bucket's first day: weeks start on Monday and months on the 1st. The first and last buckets hold only the days inside your window, so start `from` on a Monday (or the 1st) for whole buckets.

**engine** `string[]`

Engine keys to keep. Repeat the parameter (`engine=a&engine=b`) or separate values with commas. Defaults to the project's engines.

**segment** `string[]`

Segment ids to keep. Only segments the project has claimed; any other is a `400`.

**prompt** `string[]`

Prompt ids to keep.

**brand** `string[]`

Brands report only: brand ids to keep. Rows are filtered after the arithmetic, so share of voice still counts every brand in the answers, not only the ones you asked for.

**domain** `string[]`

Domains and URLs reports only: domains to keep. This narrows the rows you get back, not the work a request does, so it does not let a request cover more days.

**limit** `integer`

Rows per page, up to the maximum in the table below.

**cursor** `string`

The previous page's `next_cursor`.

**format** `string` (default: json)

`json` or `csv`. See [CSV output](https://docs.citino.ai/api/csv.md).

Each list parameter takes a limited number of values; the [API reference](https://docs.citino.ai/api-reference) has every bound.

## The response

**scope** `object`

What the rows count: `from`, `to`, `engines`, `segments`, `dimensions`, `interval` (`null` without `date`), and `prompt_scope`, which is always the unbranded set and reads `"category"`. With `segment` as a dimension, `note` says that a prompt in two segments counts in both segment rows, so segment rows can add up to more than the whole.

**items** `array`

The rows. Each carries only the dimensions you asked for: `date`, `engine`, `segment` and `prompt_id`.

**next\_cursor** `string | null`

The next page, or `null` at the end.

**has\_more** `boolean`

`true` while there is more to read.

### A brands row

**brand** `object`

`id`, `name`, and `own`, which is `true` for your brand. The `id` is what [tracking a competitor](https://docs.citino.ai/api/writes.md) takes.

**visibility, share\_of\_voice, position, sentiment** `Figure`

The four headline figures for this brand over this row's slice, each a [metric object](https://docs.citino.ai/api/metric-objects.md). With `prompt` as a dimension, every `band` is `null`: one question cannot say how much a figure would move across questions.

**mentions** `integer`

How many of the row's answers named the brand.

The brands report lists brands that were named. A brand never named in the slice has no row there.

### A domains or URLs row

**domain** `string`

The domain.

**url** `string | null`

URLs report only. `null` for an address that is not `http` or `https`.

**type, own** `string, boolean`

The kind of source, as the Sources screen labels it, and whether it is your own site.

**retrieved, cited** `integer`

How many answers retrieved the source (read it while answering) and how many cited it (named it as a source).

**answers, reported\_answers** `integer`

The answers in the row, and how many of those came from an engine that reports what it read.

**retrieval\_rate, citation\_rate** `Figure`

`retrieved` over `reported_answers`, and `cited` over `answers`. Two rates with two different denominators: retrieval can only be counted where the engine says what it read. Both carry `band: null`, as on the Sources screen.

## Budgets: how much one request may read

What a report costs is the number of days, times the prompts in scope, times the engines. So that is the budget, counted in **prompt-engine-days** rather than in days: a fixed day limit would allow a month on one project and three days on a much larger one.

| Limit | Value |
| - | - |
| Requests a minute, per API key | 60 |
| Requests a minute, per workspace (every key together) | 120 |
| Requests a minute, per signed-in app (Claude, ChatGPT) | 60 |
| Rows per page, lists | 100 |
| Rows per page, reports | 200 |
| What one report page counts as, against the limits above | 10 requests |
| Brands report without `date`: the most prompt-engine-days | 18,000 (9,000 with `segment`) |
| Domains and URLs reports without `date`: the most prompt-engine-days | 4,200 (2,100 with `segment`) |
| The longest window, with `date` as a dimension | 366 days |
| Report rows × prompts in scope, per page | 20,000 |

* **Without `date` as a dimension**, one request reads the whole window, so the window has to fit the budget. Over it, the request is `400` with a message starting `report_budget:` that gives the most days this project allows. With `segment` as a dimension the budget is smaller, as the table shows. The domains and URLs reports read answer by answer, so their budget is smaller than the brands report's.
* **With `date` as a dimension**, the window may be as long as the longest window in the table, because each page reads only its own days. Longer than that is a `400` starting `window_too_large:`.
* **A `week` or `month` interval** is refused with `report_budget:` when one bucket of your project is more than one page may read. Use `interval=day`.
* **The budget follows your project.** Add prompts or engines and the same window covers more prompt-engine-days. A window that worked last month can be refused today; with `date` as a dimension, pages simply hold fewer days.

## Paging

With `date` as a dimension, rows come date first, then engine, segment and prompt, then the brand, domain or URL. Each page reads whole buckets from where the last one stopped, until it holds `limit` rows or has read as much as one page may. So:

* **a page can hold fewer rows than `limit`**, and on the brands report a page holds at most 20,000 ÷ (prompts in scope) rows, and never more than `limit` or 200, so a large project gets shorter pages;
* **a page can be empty with `has_more` still `true`**, when the days it read held no rows;
* **follow `next_cursor` until `has_more` is `false`**, and never stop on a short page.

A cursor belongs to the exact query that produced it, `limit` included. See [Pagination](https://docs.citino.ai/api/pagination.md).

## Weight, and one at a time

A report page counts as several requests against your rate limit: the table above gives the weight. Run report requests **one after another**: while one of your workspace's reports is running, a second gets `503 busy` at once, with `Retry-After`. See [Rate limits](https://docs.citino.ai/api/rate-limits.md).

## Examples

Brands by day and engine for a window, every page:

```bash curl theme={null}
curl "https://api.citino.ai/v1/projects/$PROJECT_ID/reports/brands?from=$FROM&to=$TO&dimensions=date,engine" -H "Authorization: Bearer $CITINO_API_KEY"
```

```python Python theme={null}
import os
import requests

BASE = "https://api.citino.ai/v1"
key = os.environ["CITINO_API_KEY"]
project_id = os.environ["PROJECT_ID"]
session = requests.Session()
session.headers["Authorization"] = f"Bearer {key}"

params = {"from": os.environ["FROM"], "to": os.environ["TO"], "dimensions": "date,engine"}
rows = []
while True:
    response = session.get(f"{BASE}/projects/{project_id}/reports/brands", params=params)
    response.raise_for_status()
    page = response.json()
    rows.extend(page["items"])
    if not page["has_more"]:
        break
    params["cursor"] = page["next_cursor"]

for row in rows:
    v = row["visibility"]
    print(row["date"], row["engine"], row["brand"]["name"], v["value"], v["band"], v["n"])
```

```javascript JavaScript theme={null}
const BASE = "https://api.citino.ai/v1";
const headers = { Authorization: `Bearer ${process.env.CITINO_API_KEY}` };
const params = { from: process.env.FROM, to: process.env.TO, dimensions: "date,engine" };

const rows = [];
for (;;) {
  const query = new URLSearchParams(params);
  const response = await fetch(
    `${BASE}/projects/${process.env.PROJECT_ID}/reports/brands?${query}`,
    { headers },
  );
  if (!response.ok) throw new Error(`Citino answered ${response.status}`);
  const page = await response.json();
  rows.push(...page.items);
  if (!page.has_more) break;
  params.cursor = page.next_cursor;
}

for (const row of rows) {
  const { value, band, n } = row.visibility;
  console.log(row.date, row.engine, row.brand.name, value, band, n);
}
```

The URLs of one domain, for the same window, a week at a time:

```bash curl theme={null}
curl "https://api.citino.ai/v1/projects/$PROJECT_ID/reports/urls?from=$FROM&to=$TO&dimensions=date&interval=week&domain=$DOMAIN" -H "Authorization: Bearer $CITINO_API_KEY"
```

```python Python theme={null}
import os
import requests

BASE = "https://api.citino.ai/v1"
key = os.environ["CITINO_API_KEY"]
project_id = os.environ["PROJECT_ID"]

response = requests.get(
    f"{BASE}/projects/{project_id}/reports/urls",
    headers={"Authorization": f"Bearer {key}"},
    params={
        "from": os.environ["FROM"],
        "to": os.environ["TO"],
        "dimensions": "date",
        "interval": "week",
        "domain": os.environ["DOMAIN"],
    },
)
response.raise_for_status()
for row in response.json()["items"]:
    print(row["date"], row["url"], row["cited"], row["citation_rate"]["value"])
```

```javascript JavaScript theme={null}
const BASE = "https://api.citino.ai/v1";
const headers = { Authorization: `Bearer ${process.env.CITINO_API_KEY}` };
const query = new URLSearchParams({
  from: process.env.FROM,
  to: process.env.TO,
  dimensions: "date",
  interval: "week",
  domain: process.env.DOMAIN,
});

const response = await fetch(
  `${BASE}/projects/${process.env.PROJECT_ID}/reports/urls?${query}`,
  { headers },
);
if (!response.ok) throw new Error(`Citino answered ${response.status}`);
for (const row of (await response.json()).items) {
  console.log(row.date, row.url, row.cited, row.citation_rate.value);
}
```

These samples read one page of the URLs report; page it with the loop above for a long window.

## Related

> [card] [Pulling data nightly](https://docs.citino.ai/api/nightly-pull.md)
>
> One day at a time, the pattern that fits every plan.

> [card] [Metric objects](https://docs.citino.ai/api/metric-objects.md)
>
> Value, band, `n`, low sample and within noise.

> [card] [CSV output](https://docs.citino.ai/api/csv.md)
>
> The same rows as a spreadsheet.

> [card] [Errors](https://docs.citino.ai/api/errors.md)
>
> `report_budget`, `busy` and `query_timeout`.


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