## Filters & common parameters

- `project_id` **required** for all project-scoped endpoints.
- `metrics` or `metric`: CSV of `mentions, citations, responses, mention_rate (or visibility), citation_rate, ai_visibility_score, avg_position, avg_mention_position, sentiment_very_positive, sentiment_positive, sentiment_neutral, sentiment_negative, sentiment_very_negative, net_sentiment`
- `granularity`: `day|week|month` (weeks start Monday)
- Time window: either `range` (days) _or_`from` & `to` (ISO8601). If you send `to`, `from` is required. Defaults with `from/to`: missing `from` = 30 days ago (start of day); missing `to` = now (end of day).
- Filters: `model`, `collection_id` (tag id), `prompt` (prompt ID), `country_code`, `language_code`, `competitors` (CSV IDs)
- `include_project` (default `true`): set `false` to exclude your project (return only competitors).

## Metric definitions

These semantics are aligned 1:1 with the Overview UI.

- **mentions**: daily sums of `mentions_count`. For `week`/`month` we compute the PERIOD TOTAL (Mon-Sun for weeks) and repeat that same total for every day in the period. If a period’s total is 0, we “carry” the last non-zero period total (sticky).
- **citations**: response-level count that includes visible citations and background source references. Each actor counts at most once per response. Uses the same period-total and sticky-carry behavior as mentions.
- **responses**: total number of prompt executions (API calls to LLMs). Uses project-level execution count regardless of actor. • `day`: daily sum. • `week`/`month`: period total (no sticky carry, days with 0 executions show 0).
- **Mention Rate**`mention_rate` (also accepted as `visibility`): percentage per actor, using the project-level executions as denominator under the same filters (`mentions_count` / `prompt_executions_count` × 100). • `day`: compute the daily ratio. • `week`/`month`: compute the ratio-of-sums for the period (Σ mentions / Σ executions) and repeat that value for every day in the period. If the period's denominator is 0, carry forward the last non-null value.
- **citation_rate**: percentage of responses that include a visible or background citation for the actor (`citations_count` / `prompt_executions_count` × 100). • `day`: daily ratio. • `week`/`month`: ratio-of-period-sums with sticky carry, same as visibility.
- **AI Visibility Score**`ai_visibility_score`: position-weighted visibility metric that accounts for mention prominence. Uses reciprocal position weighting (position 1 = 100%, position 2 = 50%, position 3 = 33%, etc.). Higher scores indicate both more mentions AND better positioning. • `day`: compute daily weighted score / executions. • `week`/`month`: ratio-of-period-sums with sticky carry, same as visibility.
- **avg_position**: average visible citation position. Background citations have no visible position and are excluded. • `day`: daily average excluding nulls. • `week`/`month`: average within the period (ignoring nulls) and then propagate (carry) the last known value to days without data, matching the Overview chart smoothing.
- **avg_mention_position**: average position of the brand when mentioned in AI responses (position 1 = first mentioned, position 2 = second, etc.). Lower is better. • `day`: daily average excluding nulls. • `week`/`month`: average within the period with sticky carry.
- **Sentiment metrics**`sentiment_very_positive,sentiment_positive,sentiment_neutral,sentiment_negative,sentiment_very_negative`: for each actor and day, percentage of sentiments in that bucket over all sentiments for that actor. • `day`: daily ratio (`bucket_count / total_sentiments * 100`). Days with no sentiments return `null`. • `week`/`month`: we compute a ratio-of-period-sums (`Σ bucket / Σ total * 100`) and repeat that percentage for every day in the period. If the period’s denominator is 0, we carry forward the last non-null value (sticky), matching the Overview smoothing.
- **Net Sentiment Score**`net_sentiment`: for each actor and day, we calculate a score in [-100, 100]: ( (very_positive + positive) − (negative + very_negative) ) / total_sentiments × 100. • `day`: using the daily percentages of each bucket (the metrics `sentiment_*`) we calculate `net = (pos+very_pos) − (neg+very_neg)`. • `week`/`month`: the score inherits the same aggregation/smoothing semantics as the sentiment buckets because it is calculated from those series.

In `/metrics/summary`, `total` is a sum for count metrics and an average for `avg_position`, `avg_mention_position`, and `net_sentiment`.

## Output Formats for BI Tools

Read endpoints return nested JSON by default, which is convenient for code but unreadable for business intelligence tools. Pass the optional `output` parameter to get the same data as a rectangular table that Tableau, Excel, Google Sheets or a warehouse loader can consume directly.

- `output` omitted (default): the nested JSON documented for each endpoint. Unchanged, so existing integrations are unaffected.
- `output=flat`: the same metadata plus `columns` (ordered column names), `rows` (one flat object per row) and `row_count`.
- `output=csv`: the same rows as `text/csv`, with `columns` as the header row.

Supported on `/metrics/timeseries`, `/metrics/summary`, `/metrics/sov`, `/metrics/prompt_summary`, `/metrics/top_sources`, the `/search_console/*` series and the list-returning `/dimensions/*` endpoints. Endpoints whose payload is not a single table (`/metrics/agent_traffic`, `/metrics/ai_traffic`, `/search_console/summary`, `/dimensions/models`, `/dimensions/locales`) reject `output` instead of ignoring it.

`/metrics/sov` also accepts `view=over_time` (default), `view=current` or `view=breakdown`: its payload holds several different shapes and a table can only hold one at a time.

### Columns by endpoint

| Endpoint | Columns |
| --- | --- |
| `/metrics/timeseries` | `date, actor_type, actor_id, actor_name, actor_domain, metric, value` |
| `/metrics/summary` | `actor_type, actor_id, actor_name, actor_domain, metric, aggregation, total, min, max, last` |
| `/metrics/sov` | `date, actor_type, actor_id, actor_name, actor_domain, share` |
| `/metrics/sov?view=current` | `actor_type, actor_id, actor_name, actor_domain, share` |
| `/metrics/sov?view=breakdown` | `rank, actor_type, actor_id, actor_name, actor_domain, share, others` |
| `/metrics/prompt_summary, /metrics/top_sources, /search_console/*, /dimensions/*` | The keys of the endpoint's own data rows |

### Example

```
GET /api/v1/metrics/timeseries?project_id=1&range=30&metrics=mentions&output=csv

date,actor_type,actor_id,actor_name,actor_domain,metric,value
2025-01-01,project,1,My Brand,mybrand.com,mentions,10
2025-01-01,competitor,2,Competitor A,competitor.com,mentions,5
```

Notes:

- Errors are always returned as JSON, never as CSV, so a failure is never mistaken for data.
- Percentages stay on the raw 0-100 scale, exactly as in the JSON response.
- Text cells that begin with an equals, plus, minus or at sign are escaped so spreadsheets do not execute them as formulas.
- Paginated endpoints also return their paging metadata in the `X-Total-Count`, `X-Page` and `X-Per-Page` response headers, which a CSV body cannot carry.
- Flat output is capped at 200,000 rows. Above that, narrow the date range, request fewer metrics, or use `granularity=week`.
- Endpoints without a flat projection reject `output` with `ERR_INVALID_PARAM` instead of ignoring it.

## Cache & Conditional GET

Metrics endpoints (`timeseries`, `summary`, `sov`, `top_sources`) support conditional GET. We compute an `ETag` from the project version and a hash of your query params, and use the project’s `updated_at` as `Last-Modified`.

Send `If-None-Match` or `If-Modified-Since` to receive `304 Not Modified` when nothing changed.

## Errors

Errors use a consistent JSON structure. Branch on the code field. The message field contains error details when available; otherwise, it contains a default token such as missing_authorization:

| Code | HTTP | Meaning |
| --- | --- | --- |
| ERR_MISSING_AUTH | 401 | Missing or malformed Authorization header |
| ERR_INVALID_API_KEY | 401 | Token not recognized |
| ERR_REVOKED_API_KEY | 403 | Key revoked |
| ERR_PROJECT_NOT_FOUND | 404 | project_id not accessible by this user |
| ERR_NOT_FOUND | 404 | Resource not found |
| ERR_INVALID_PARAM | 422 | Validation failed (see message) |
| ERR_LIMIT_REACHED | 422 | Project, prompt, technical GEO report or recommendation limit reached (the message says which) |
| ERR_QUOTA_EXCEEDED | 422 | Competitor, GEO Writer task or webhook subscription cap reached (the message says which) |

Error body shape:

Typical 422 cases:

- `invalid metrics`: only `mentions, citations, responses, mention_rate (or visibility), citation_rate, ai_visibility_score, avg_position, avg_mention_position, sentiment_very_positive, sentiment_positive, sentiment_neutral, sentiment_negative, sentiment_very_negative, net_sentiment` are allowed.
- `invalid granularity`: must be `day|week|month`.
- `from/to must be ISO8601`, or `from is required with to`.
- `range is required` when neither `from` nor `to` are sent.
- `unknown competitor ids: ...`: IDs not found under the project.

## Versioning & Rate limits

Current version: `v1`. Future breaking changes will bump the path (e.g. `/api/v2`).

Rate limiting: 300 requests per minute per API key. Contact us for higher quotas.

Metrics endpoints support conditional GET (`ETag`/`Last-Modified`); see [Cache & Conditional GET](/content/api-docs/core-concepts#cache/index.html).
