# Visibility analytics

Source: https://ceyo.ai/docs/signal/visibility

### Visibility

Measure brand visibility, compare competitors, and track performance trends across AI models for a project or location.

**Scope:** Project

> **Percentage metrics**
>
> Visibility, coverage, and share of voice are percentages from 0 to 100. Competitor timeseries are keyed by tracked entity, with selected model keys returned separately in `models`.

### Get visibility overview

`GET /projects/{project_id}/visibility/summary`

Returns aggregate visibility and citation metrics for matching responses in the selected project.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Project identifier. |

#### Query parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `start` | date | Optional; Default: 6 days before end | First run date to include, in YYYY-MM-DD format. |
| `end` | date | Optional; Default: Today | Last run date to include, in YYYY-MM-DD format. |
| `models` | string | Optional; Default: All model keys present in matching responses | Comma-separated or repeated model keys, such as chatgpt,claude,perplexity. |
| `topic_ids` | string | Optional | Comma-separated or repeated topic UUIDs. |
| `topic_id` | uuid | Optional | Alias for topic\_ids when filtering by one topic UUID. |
| `prompt_id` | uuid | Optional | Restrict results to one prompt in the selected scope. |

#### Response envelope

`start`:**date**`end`:**date**`models`:**string\[\]**`summary`:**object**

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `start` | date |  | Resolved start date. |
| `end` | date |  | Resolved end date. |
| `models` | string\[\] |  | Model keys included in the calculation. When omitted from the request, this contains every model key present in matching responses. |
| `summary` | object |  | Aggregate visibility metrics. |

#### Summary object

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `primary_mentions` | integer |  | Responses in which the primary tracked entity was mentioned. |
| `visibility_percentage` | number |  | Share of responses that mentioned the primary tracked entity, rounded to two decimals. |
| `average_position` | number \| null |  | Average 1-based position for primary-entity mentions with a detected position. |
| `citations` | integer |  | Total citation occurrences across matching responses. |

> **Metric definitions**
>
> `visibility_percentage` is the share of matching responses that mention the primary entity. `average_position` uses only primary mentions with a detected position, and `citations` counts citation occurrences.

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/projects/{project_id}/visibility/summary?start=2026-07-24&end=2026-07-30&models=chatgpt,perplexity' \
  --header 'Authorization: Bearer ceyo_platform_...'
```

```json
{
  "start": "2026-07-24",
  "end": "2026-07-30",
  "models": ["chatgpt", "perplexity"],
  "summary": {
    "primary_mentions": 17,
    "visibility_percentage": 42.5,
    "average_position": 2.82,
    "citations": 31
  }
}
```

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": [
      {
        "field": "name",
        "message": "must be present"
      }
    ],
    "request_id": "req_01K1GP6J8QQFZ4D2B6C5A9V3TS"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `400` | invalid\_date\_range \| range\_too\_large |  | A date is invalid or the requested window exceeds three months. |
| `401` | invalid\_api\_key |  | The Bearer API key is missing or invalid. |
| `403` | forbidden |  | The API key is valid but is not allowed to access this resource. |
| `404` | not\_found |  | The project, location, or visibility scope was not found. |
| `422` | visibility\_unavailable |  | Project-level visibility is unavailable for a locations-only container; use a location scope. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Compare competitors

`GET /projects/{project_id}/visibility/competitors`

Compares the primary entity plus tracked competitors for the selected project, ordered with primary first, then by share\_of\_voice descending and name.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Project identifier. |

#### Query parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `start` | date | Optional; Default: 6 days before end | First run date to include, in YYYY-MM-DD format. |
| `end` | date | Optional; Default: Today | Last run date to include, in YYYY-MM-DD format. |
| `models` | string | Optional; Default: All model keys present in matching responses | Comma-separated or repeated model keys, such as chatgpt,claude,perplexity. |
| `topic_ids` | string | Optional | Comma-separated or repeated topic UUIDs. |
| `topic_id` | uuid | Optional | Alias for topic\_ids when filtering by one topic UUID. |
| `prompt_id` | uuid | Optional | Restrict results to one prompt in the selected scope. |

#### Response envelope

`start`:**date**`end`:**date**`models`:**string\[\]**`total_mentions`:**integer**`entities`:**object\[\]**

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `start` | date |  | Resolved start date. |
| `end` | date |  | Resolved end date. |
| `models` | string\[\] |  | Model keys included in the calculation. When omitted from the request, this contains every model key present in matching responses. |
| `total_mentions` | integer |  | Mentions across all returned entities. |
| `entities` | object\[\] |  | The primary entity plus tracked competitors, ordered with primary first, then by share\_of\_voice descending and name. |

#### Entity

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `tracked_entity_id` | uuid |  | Tracked primary brand or competitor identifier. |
| `kind` | primary \| competitor |  | Entity relationship to the selected scope. |
| `name` | string |  | Tracked entity name. |
| `domain` | string \| null |  | Normalized entity domain when configured. |
| `status` | active |  | Current tracked-entity status. Only active entities are returned. |
| `mentions` | integer |  | Responses in which this entity was mentioned. |
| `coverage_percentage` | number |  | Share of matching responses that mentioned the entity. |
| `share_of_voice` | number |  | Entity mentions divided by mentions across all returned entities, multiplied by 100. |
| `average_position` | number \| null |  | Average detected 1-based position when the entity was mentioned. |

> **Metric definitions**
>
> Coverage uses all matching responses as its denominator. Share of voice uses mentions across the returned primary entity and tracked competitors. Results place the primary entity first, followed by competitors ordered by `share_of_voice` descending and then name. When there are no mentions, both percentages are `0.0` and average position is `null`.

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/projects/{project_id}/visibility/competitors?start=2026-07-24&end=2026-07-30&topic_ids=ab20526b-6bb2-436c-8c93-5bf77ea43848' \
  --header 'Authorization: Bearer ceyo_platform_...'
```

```json
{
  "start": "2026-07-24",
  "end": "2026-07-30",
  "models": ["chatgpt", "perplexity"],
  "total_mentions": 33,
  "entities": [
    {
      "tracked_entity_id": "f3385d92-8f24-49f2-9819-18412e93427f",
      "kind": "primary",
      "name": "Ceyo",
      "domain": "ceyo.ai",
      "status": "active",
      "mentions": 17,
      "coverage_percentage": 42.5,
      "share_of_voice": 51.52,
      "average_position": 2.82
    },
    {
      "tracked_entity_id": "2e230599-4918-49a7-bb08-b30711836aa8",
      "kind": "competitor",
      "name": "Example Competitor",
      "domain": "competitor.example",
      "status": "active",
      "mentions": 16,
      "coverage_percentage": 40.0,
      "share_of_voice": 48.48,
      "average_position": 3.13
    }
  ]
}
```

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": [
      {
        "field": "name",
        "message": "must be present"
      }
    ],
    "request_id": "req_01K1GP6J8QQFZ4D2B6C5A9V3TS"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `400` | invalid\_date\_range \| range\_too\_large |  | A date is invalid or the requested window exceeds three months. |
| `401` | invalid\_api\_key |  | The Bearer API key is missing or invalid. |
| `403` | forbidden |  | The API key is valid but is not allowed to access this resource. |
| `404` | not\_found |  | The project, location, or visibility scope was not found. |
| `422` | visibility\_unavailable |  | Project-level visibility is unavailable for a locations-only container; use a location scope. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Get competitor trends

`GET /projects/{project_id}/visibility/competitors/timeseries`

Returns dated coverage, mention, and position series for the primary entity and competitors in the selected project.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Project identifier. |

#### Query parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `start` | date | Optional; Default: 6 days before end | First run date to include, in YYYY-MM-DD format. |
| `end` | date | Optional; Default: Today | Last run date to include, in YYYY-MM-DD format. |
| `models` | string | Optional; Default: All model keys present in matching responses | Comma-separated or repeated model keys, such as chatgpt,claude,perplexity. |
| `topic_ids` | string | Optional | Comma-separated or repeated topic UUIDs. |
| `topic_id` | uuid | Optional | Alias for topic\_ids when filtering by one topic UUID. |
| `prompt_id` | uuid | Optional | Restrict results to one prompt in the selected scope. |

#### Response envelope

`start`:**date**`end`:**date**`models`:**string\[\]**`entities`:**object\[\]**`points`:**object\[\]**

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `start` | date |  | Resolved start date. |
| `end` | date |  | Resolved end date. |
| `models` | string\[\] |  | Model keys included in the calculation. When omitted from the request, this contains every model key present in matching responses. |
| `entities` | object\[\] |  | Metadata for each keyed series. |
| `points` | object\[\] |  | One point for each date containing matching responses. |

#### Timeseries entity

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `key` | string |  | Stable series key in the form entity:{tracked\_entity\_id}. |
| `tracked_entity_id` | uuid |  | Tracked entity identifier. |
| `kind` | primary \| competitor |  | Entity relationship to the selected scope. |
| `name` | string |  | Tracked entity name. |
| `domain` | string \| null |  | Normalized entity domain when configured. |
| `aliases` | string\[\] |  | Additional names recognized as mentions of this entity. |

#### Timeseries point

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `date` | date |  | Visibility measurement date. |
| `mentions` | object |  | Mention count keyed by entity series key. |
| `average_positions` | object |  | Average 1-based position or null, keyed by entity series key. |

> **Series keys and dates**
>
> Join each object in `mentions` and `average_positions` to entity metadata through `key`. Dates without matching responses are omitted; returned entity values are zero-filled for dates that have responses.

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/projects/{project_id}/visibility/competitors/timeseries?start=2026-07-24&end=2026-07-30&models=chatgpt,perplexity' \
  --header 'Authorization: Bearer ceyo_platform_...'
```

```json
{
  "start": "2026-07-24",
  "end": "2026-07-30",
  "models": ["chatgpt", "perplexity"],
  "entities": [
    {
      "key": "entity:f3385d92-8f24-49f2-9819-18412e93427f",
      "tracked_entity_id": "f3385d92-8f24-49f2-9819-18412e93427f",
      "kind": "primary",
      "name": "Ceyo",
      "domain": "ceyo.ai",
      "aliases": ["Ceyo AI"]
    }
  ],
  "points": [
    {
      "date": "2026-07-30",
      "mentions": {
        "entity:f3385d92-8f24-49f2-9819-18412e93427f": 4
      },
      "average_positions": {
        "entity:f3385d92-8f24-49f2-9819-18412e93427f": 2.5
      }
    }
  ]
}
```

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": [
      {
        "field": "name",
        "message": "must be present"
      }
    ],
    "request_id": "req_01K1GP6J8QQFZ4D2B6C5A9V3TS"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `400` | invalid\_date\_range \| range\_too\_large |  | A date is invalid or the requested window exceeds three months. |
| `401` | invalid\_api\_key |  | The Bearer API key is missing or invalid. |
| `403` | forbidden |  | The API key is valid but is not allowed to access this resource. |
| `404` | not\_found |  | The project, location, or visibility scope was not found. |
| `422` | visibility\_unavailable |  | Project-level visibility is unavailable for a locations-only container; use a location scope. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Get model visibility trends

`GET /projects/{project_id}/visibility/model-trends`

Returns daily and range-level primary-brand visibility by model, plus an aggregate across models, for the selected project.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Project identifier. |

#### Query parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `start` | date | Optional; Default: 6 days before end | First run date to include, in YYYY-MM-DD format. |
| `end` | date | Optional; Default: Today | Last run date to include, in YYYY-MM-DD format. |
| `models` | string | Optional; Default: All model keys present in matching responses | Comma-separated or repeated model keys, such as chatgpt,claude,perplexity. |
| `topic_ids` | string | Optional | Comma-separated or repeated topic UUIDs. |
| `topic_id` | uuid | Optional | Alias for topic\_ids when filtering by one topic UUID. |
| `prompt_id` | uuid | Optional | Restrict results to one prompt in the selected scope. |

#### Response envelope

`start`:**date**`end`:**date**`models`:**string\[\]**`aggregate`:**object**`model_summaries`:**object\[\]**`days`:**object\[\]**

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `start` | date |  | Resolved start date. |
| `end` | date |  | Resolved end date. |
| `models` | string\[\] |  | Model keys included in the calculation. When omitted from the request, this contains every model key present in matching responses. |
| `aggregate` | object |  | Primary-brand visibility across all returned models. |
| `model_summaries` | object\[\] |  | Range-level primary-brand visibility for each returned model, ordered by model key. |
| `days` | object\[\] |  | Daily primary-brand visibility in ascending date order. Dates without matching responses are omitted. |

#### Aggregate object

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `mentions` | integer |  | Responses in which the primary tracked brand was mentioned across all returned models. |
| `visibility_percentage` | number |  | Share of responses that mentioned the primary tracked brand, rounded to two decimals. |
| `comparison` | object \| null |  | Comparison with the immediately preceding window of the same length, or null when that window has no matching responses. |

#### Model summary object

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `model` | string |  | Stable model key. |
| `mentions` | integer |  | Responses for this model in which the primary tracked brand was mentioned. |
| `visibility_percentage` | number |  | Share of this model’s responses that mentioned the primary tracked brand, rounded to two decimals. |
| `comparison` | object \| null |  | Comparison with the same model in the immediately preceding window, or null when that model has no matching responses in the comparison window. |

#### Daily object

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `date` | date |  | Visibility measurement date in YYYY-MM-DD format. |
| `aggregate` | object |  | Primary-brand visibility across all returned models for this date. |
| `models` | object\[\] |  | Primary-brand visibility for each returned model on this date, ordered by model key. |

#### Daily aggregate object

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `mentions` | integer |  | Responses across all returned models that mentioned the primary tracked brand on this date. |
| `visibility_percentage` | number |  | Share of responses that mentioned the primary tracked brand on this date. |

#### Daily model object

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `model` | string |  | Stable model key. |
| `mentions` | integer |  | Responses for this model that mentioned the primary tracked brand on this date. |
| `visibility_percentage` | number |  | Share of this model’s responses that mentioned the primary tracked brand on this date. |

#### Comparison object

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `start` | date |  | First date of the immediately preceding comparison window. |
| `end` | date |  | Last date of the immediately preceding comparison window. |
| `mentions` | integer |  | Responses that mentioned the primary tracked brand in the comparison window. |
| `visibility_percentage` | number |  | Primary-brand visibility in the comparison window, expressed as a percentage from 0 to 100. |
| `delta_percentage_points` | number |  | Selected-window visibility\_percentage minus comparison-window visibility\_percentage, in percentage points. |

> **Ranges, percentages, and comparisons**
>
> The default range is seven days and the maximum range is three months. Every `visibility_percentage` is a value from 0 to 100 calculated as the share of responses that mention the primary brand. Range-level comparisons use the immediately preceding window of equal length. A positive `delta_percentage_points` means visibility increased. Dates without matching responses are omitted.

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/projects/{project_id}/visibility/model-trends?start=2026-07-24&end=2026-07-30&models=chatgpt,perplexity' \
  --header 'Authorization: Bearer ceyo_platform_...'
```

```json
{
  "start": "2026-07-24",
  "end": "2026-07-30",
  "models": ["chatgpt", "perplexity"],
  "aggregate": {
    "mentions": 17,
    "visibility_percentage": 42.5,
    "comparison": {
      "start": "2026-07-17",
      "end": "2026-07-23",
      "mentions": 14,
      "visibility_percentage": 36.84,
      "delta_percentage_points": 5.66
    }
  },
  "model_summaries": [
    {
      "model": "chatgpt",
      "mentions": 10,
      "visibility_percentage": 50.0,
      "comparison": {
        "start": "2026-07-17",
        "end": "2026-07-23",
        "mentions": 8,
        "visibility_percentage": 42.11,
        "delta_percentage_points": 7.89
      }
    },
    {
      "model": "perplexity",
      "mentions": 7,
      "visibility_percentage": 35.0,
      "comparison": {
        "start": "2026-07-17",
        "end": "2026-07-23",
        "mentions": 6,
        "visibility_percentage": 31.58,
        "delta_percentage_points": 3.42
      }
    }
  ],
  "days": [
    {
      "date": "2026-07-30",
      "aggregate": {
        "mentions": 4,
        "visibility_percentage": 50.0
      },
      "models": [
        {
          "model": "chatgpt",
          "mentions": 3,
          "visibility_percentage": 75.0
        },
        {
          "model": "perplexity",
          "mentions": 1,
          "visibility_percentage": 25.0
        }
      ]
    }
  ]
}
```

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": [
      {
        "field": "name",
        "message": "must be present"
      }
    ],
    "request_id": "req_01K1GP6J8QQFZ4D2B6C5A9V3TS"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `400` | invalid\_date\_range \| range\_too\_large |  | A date is invalid or the requested window exceeds three months. |
| `401` | invalid\_api\_key |  | The Bearer API key is missing or invalid. |
| `403` | forbidden |  | The API key is valid but is not allowed to access this resource. |
| `404` | not\_found |  | The project, location, or visibility scope was not found. |
| `422` | visibility\_unavailable |  | Project-level visibility is unavailable for a locations-only container; use a location scope. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |
