## Filtros y parámetros comunes

- `project_id` **obligatorio** para todos los endpoints con ámbito de proyecto.
- `metrics` o `metric`: CSV con `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` (las semanas empiezan en lunes)
- Ventana de tiempo: o bien `range` (días) _o_`from` y `to` (ISO8601). Si envías `to`, `from` es obligatorio. Valores por defecto con `from/to`: si falta `from` = hace 30 días (inicio del día); si falta `to` = ahora (fin del día).
- Filtros: `model`, `collection_id` (ID de tag), `prompt` (ID de prompt), `country_code`, `language_code`, `competitors` (IDs en CSV)
- `include_project` (por defecto `true`): establece `false` para excluir tu proyecto (devuelve solo competidores).

## Definición de métricas

Estas semánticas están alineadas 1:1 con la interfaz de Resumen.

- **mentions**: sumas diarias de `mentions_count`. Para `week`/`month` calculamos el TOTAL DEL PERÍODO (de lunes a domingo en las semanas) y repetimos ese mismo total para todos los días del período. Si el total de un período es 0, «arrastramos» el último total de período distinto de cero (arrastre persistente).
- **citations**: recuento a nivel de respuesta que incluye las citaciones visibles y las referencias a fuentes en segundo plano. Cada actor cuenta como máximo una vez por respuesta. Usa el mismo comportamiento de total del período y arrastre persistente que las menciones.
- **responses**: número total de ejecuciones de prompt (llamadas de API a los LLM). Usa el recuento de ejecuciones del proyecto, independientemente del actor. • `day`: suma diaria. • `week`/`month`: total del período (sin arrastre persistente; los días con 0 ejecuciones muestran 0).
- **Tasa de mención**`mention_rate` (también aceptado como `visibility`): porcentaje por actor, usando las ejecuciones del proyecto como denominador con los mismos filtros (`mentions_count` / `prompt_executions_count` × 100). • `day`: calcula la proporción diaria. • `week`/`month`: calcula la proporción de sumas del período (Σ menciones / Σ ejecuciones) y repite ese valor para todos los días del período. Si el denominador del período es 0, arrastra el último valor no nulo.
- **citation_rate**: porcentaje de respuestas que incluyen una citación visible o en segundo plano para el actor (`citations_count` / `prompt_executions_count` × 100). • `day`: proporción diaria. • `week`/`month`: proporción de sumas del período con arrastre persistente, igual que la visibilidad.
- **AI Visibility Score**`ai_visibility_score`: métrica de visibilidad ponderada por posición que tiene en cuenta la prominencia de la mención. Usa ponderación recíproca por posición (posición 1 = 100%, posición 2 = 50%, posición 3 = 33%, etc.). Las puntuaciones más altas indican tanto más menciones como mejor posicionamiento. • `day`: calcula la puntuación ponderada diaria / ejecuciones. • `week`/`month`: proporción de sumas del período con arrastre persistente, igual que la visibilidad.
- **avg_position**: posición media de las citaciones visibles. Las citaciones en segundo plano no tienen posición visible y quedan excluidas. • `day`: media diaria excluyendo los nulos. • `week`/`month`: media dentro del período (ignorando los nulos) y después se propaga (arrastra) el último valor conocido a los días sin datos, igual que el suavizado del gráfico de Resumen.
- **avg_mention_position**: posición media de la marca cuando aparece mencionada en las respuestas de IA (posición 1 = primera mención, posición 2 = segunda, etc.). Cuanto más baja, mejor. • `day`: media diaria excluyendo los nulos. • `week`/`month`: media dentro del período con arrastre persistente.
- **Métricas de sentimiento**`sentiment_very_positive,sentiment_positive,sentiment_neutral,sentiment_negative,sentiment_very_negative`: para cada actor y día, porcentaje de sentimientos de esa categoría sobre todos los sentimientos de ese actor. • `day`: proporción diaria (`bucket_count / total_sentiments * 100`). Los días sin sentimientos devuelven `null`. • `week`/`month`: calculamos una proporción de sumas del período (`Σ bucket / Σ total * 100`) y repetimos ese porcentaje para todos los días del período. Si el denominador del período es 0, arrastramos el último valor no nulo (arrastre persistente), igual que el suavizado del Resumen.
- **Puntuación de sentimiento neto**`net_sentiment`: para cada actor y día, calculamos una puntuación en \[-100, 100\]: ( (very_positive + positive) − (negative + very_negative) ) / total_sentiments × 100. • `day`: usamos los porcentajes diarios de cada categoría (las métricas `sentiment_*`) y calculamos `net = (pos+very_pos) − (neg+very_neg)`. • `week`/`month`: la puntuación hereda las mismas semánticas de agregación y suavizado que las categorías de sentimiento, porque se calcula a partir de esas series.

En `/metrics/summary`, `total` es una suma para las métricas de recuento y una media para `avg_position`, `avg_mention_position` y `net_sentiment`.

## Formatos de salida para herramientas de BI

Los endpoints de lectura devuelven JSON anidado por defecto, lo que resulta cómodo para el código pero ilegible para las herramientas de business intelligence. Pasa el parámetro opcional `output` para obtener los mismos datos como una tabla rectangular que Tableau, Excel, Google Sheets o un cargador de data warehouse puedan consumir directamente.

- `output` omitido (por defecto): el JSON anidado documentado para cada endpoint. Sin cambios, por lo que las integraciones existentes no se ven afectadas.
- `output=flat`: los mismos metadatos más `columns` (nombres de columna ordenados), `rows` (un objeto plano por fila) y `row_count`.
- `output=csv`: las mismas filas que `text/csv`, con `columns` como fila de encabezado.

Disponible en `/metrics/timeseries`, `/metrics/summary`, `/metrics/sov`, `/metrics/prompt_summary`, `/metrics/top_sources`, la serie `/search_console/*` y los endpoints `/dimensions/*` que devuelven listas. Los endpoints cuyo payload no es una única tabla (`/metrics/agent_traffic`, `/metrics/ai_traffic`, `/search_console/summary`, `/dimensions/models`, `/dimensions/locales`) rechazan `output` en lugar de ignorarlo.

`/metrics/sov` también acepta `view=over_time` (por defecto), `view=current` o `view=breakdown`: su payload contiene varias formas distintas y una tabla solo puede contener una a la vez.

### Columnas por 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 |

### Ejemplo

```
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
```

Notas:

- Los errores siempre se devuelven como JSON, nunca como CSV, por lo que un fallo nunca se confunde con datos.
- Los porcentajes se mantienen en la escala bruta de 0 a 100, exactamente igual que en la respuesta JSON.
- Las celdas de texto que empiezan por un signo igual, más, menos o arroba se escapan para que las hojas de cálculo no las ejecuten como fórmulas.
- Los endpoints paginados también devuelven sus metadatos de paginación en las cabeceras de respuesta `X-Total-Count`, `X-Page` y `X-Per-Page`, que un cuerpo CSV no puede transportar.
- La salida plana tiene un límite de 200.000 filas. A partir de ahí, reduce el rango de fechas, solicita menos métricas o usa `granularity=week`.
- Los endpoints sin proyección plana rechazan `output` con `ERR_INVALID_PARAM` en lugar de ignorarlo.

## Caché y GET condicional

Los endpoints de métricas (`timeseries`, `summary`, `sov`, `top_sources`) admiten GET condicional. Calculamos un `ETag` a partir de la versión del proyecto y de un hash de tus parámetros de consulta, y usamos el `updated_at` del proyecto como `Last-Modified`.

Envía `If-None-Match` o `If-Modified-Since` para recibir `304 Not Modified` cuando nada haya cambiado.

## Errores

Los errores siguen una estructura JSON uniforme. Basa la gestión de los errores en el campo code. El campo message contiene los detalles del error cuando están disponibles; de lo contrario, contiene un identificador predeterminado, como missing_authorization:

| Código | HTTP | Significado |
| --- | --- | --- |
| ERR_MISSING_AUTH | 401 | Falta el encabezado Authorization o está mal formado |
| ERR_INVALID_API_KEY | 401 | Token no reconocido |
| ERR_REVOKED_API_KEY | 403 | Clave revocada |
| ERR_PROJECT_NOT_FOUND | 404 | project_id no accesible para este usuario |
| ERR_NOT_FOUND | 404 | Recurso no encontrado |
| ERR_INVALID_PARAM | 422 | Error de validación (consulta el mensaje) |
| ERR_LIMIT_REACHED | 422 | Límite de proyectos, prompts, informes técnicos GEO o recomendaciones alcanzado (el mensaje indica cuál) |
| ERR_QUOTA_EXCEEDED | 422 | Tope de competidores, tareas de GEO Writer o suscripciones de webhooks alcanzado (el mensaje indica cuál) |

Estructura del cuerpo de error:

Casos típicos de 422:

- `invalid metrics`: solo se permiten `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`.
- `invalid granularity`: debe ser `day|week|month`.
- `from/to must be ISO8601`, o `from is required with to`.
- `range is required` cuando no se envía ni `from` ni `to`.
- `unknown competitor ids: ...`: IDs no encontrados en el proyecto.

## Versiones y límites de solicitudes

Versión actual: `v1`. Los futuros cambios incompatibles incrementarán la versión de la ruta (p. ej. `/api/v2`).

Límite de solicitudes: 300 solicitudes por minuto y clave de API. Contacta con nosotros para cuotas superiores.

Los endpoints de métricas admiten GET condicional (`ETag`/`Last-Modified`); consulta [Caché y GET condicional](/content/es/api-docs/core-concepts#cache/index.html).
