# API de estadísticas

> Consulta por API las visitas, series por hora, día, semana, mes o trimestre, desgloses, objetivos y embudos de tus webs con una clave de solo lectura.

La API pública de Limecount te devuelve por HTTP las mismas cifras que ves en el panel: totales, series temporales, desgloses (páginas, fuentes, países, dispositivos, eventos…), objetivos y embudos. Sirve para tus informes, tus propios cuadros de mando, hojas de cálculo o para que una IA responda preguntas sobre tu tráfico.

> La API es de **solo lectura** y solo ofrece **datos agregados**. No existe ningún dato por visitante: ni identificadores, ni IPs, ni listas de sesiones. Los desgloses omiten las filas con menos de 3 resultados para que ninguna cifra señale a una persona concreta.

Está disponible en todos los planes y durante la prueba. La URL base es:

```text
https://api.limecount.com/api/v1
```

## Crear una clave

Solo los propietarios y administradores pueden gestionar claves.

1. Abre **Ajustes de organización** y baja hasta **Claves de API**.
2. Pulsa **Crear clave**, ponle un nombre (de 1 a 60 caracteres) y elige a qué webs da acceso: todas las de la organización (también las futuras) o solo algunas.
3. Copia la clave que aparece. **Solo se muestra una vez**: Limecount guarda únicamente una huella, así que si la pierdes tendrás que crear otra.

Una clave empieza por `lck_` y tiene 47 caracteres. En la lista verás su nombre, su prefijo (`lck_` y los 8 primeros caracteres), las webs permitidas, quién la creó y cuándo se usó por última vez. Cada organización puede tener hasta **10 claves activas**.

Para quitar el acceso, pulsa **Revocar**: la clave deja de funcionar al instante y no se puede reactivar. Crear y revocar claves queda en el registro de actividad, sin la clave.

> Trata la clave como una contraseña: no la pongas en el código de una web pública ni en un repositorio. Quien la tenga puede leer las estadísticas de las webs a las que da acceso. Usa una clave por aplicación, restringida a las webs que necesite, y revócala si sospechas que se ha filtrado.

## Autenticación

Envía la clave en la cabecera `Authorization` de cada petición:

```text
Authorization: Bearer lck_tu_clave
```

No se aceptan cookies ni el inicio de sesión del panel. La API admite peticiones desde cualquier origen (CORS abierto, sin credenciales), pero no la llames desde el navegador de tus visitantes: la clave quedaría a la vista. Úsala desde tu servidor, un script o una herramienta interna.

## Primera petición

Lista tus webs para obtener su `id`:

```bash
curl -s https://api.limecount.com/api/v1/sites \
  -H "Authorization: Bearer $LIMECOUNT_API_KEY"
```

```json
{
  "data": [
    {
      "id": "665f1c2e9d1b2a0012a3b4c5",
      "domain": "midominio.com",
      "name": "midominio.com",
      "timezone": "Europe/Madrid",
      "createdAt": "2026-03-02T10:15:00.000Z",
      "verifiedAt": "2026-03-02T10:21:40.000Z"
    }
  ],
  "meta": { "total": 1 }
}
```

Con ese `id` pide, por ejemplo, las visitas por trimestre del último año:

```bash
curl -s "https://api.limecount.com/api/v1/stats/timeseries?site_id=665f1c2e9d1b2a0012a3b4c5&from=2025-11-01&to=2026-10-31&interval=quarter" \
  -H "Authorization: Bearer $LIMECOUNT_API_KEY"
```

```json
{
  "data": {
    "points": [
      { "date": "2025-10-01", "pageviews": 9120, "visits": 4310, "events": 380 },
      { "date": "2026-01-01", "pageviews": 10544, "visits": 5022, "events": 411 },
      { "date": "2026-04-01", "pageviews": 11890, "visits": 5604, "events": 502 },
      { "date": "2026-07-01", "pageviews": 12301, "visits": 5873, "events": 530 },
      { "date": "2026-10-01", "pageviews": 3110, "visits": 1490, "events": 140 }
    ]
  },
  "meta": {
    "site_id": "665f1c2e9d1b2a0012a3b4c5",
    "from": "2025-11-01",
    "to": "2026-10-31",
    "timezone": "Europe/Madrid",
    "interval": "quarter"
  }
}
```

Los trimestres de los extremos pueden ser parciales (aquí el primero empieza el 1 de noviembre y el último termina el 31 de octubre), porque solo cuentan los días del rango pedido.

La misma consulta con `fetch` en JavaScript (Node 18 o superior):

```js
const API = 'https://api.limecount.com/api/v1';
const headers = { Authorization: `Bearer ${process.env.LIMECOUNT_API_KEY}` };

const { data: sites } = await (await fetch(`${API}/sites`, { headers })).json();

const params = new URLSearchParams({
  site_id: sites[0].id,
  period: 'custom',
  from: '2025-11-01',
  to: '2026-10-31',
  interval: 'quarter',
});
const res = await fetch(`${API}/stats/timeseries?${params}`, { headers });
if (!res.ok) throw new Error((await res.json()).error.message);

const { data } = await res.json();
for (const p of data.points) console.log(p.date, p.visits);
```

## Respuestas

Todas las respuestas correctas son JSON con esta forma:

```json
{
  "data": {},
  "meta": {
    "site_id": "665f1c2e9d1b2a0012a3b4c5",
    "from": "2026-10-06",
    "to": "2026-11-04",
    "timezone": "Europe/Madrid",
    "interval": "day",
    "previous": { "from": "2026-09-06", "to": "2026-10-05" },
    "history": { "days": 180, "availableFrom": "2026-05-09", "limited": false }
  }
}
```

`meta.history` indica el historial que permite el plan de la organización: `days` son los días visibles, `availableFrom` el primer día consultable (en la zona horaria de la web) y `limited` vale `true` cuando el rango pedido se ha recortado por ese límite. Si todo el rango queda fuera, la respuesta llega vacía, sin error. Consulta [plan y facturación](https://limecount.com/docs/plan-y-facturacion.md).

`from` y `to` son el primer y el último día incluidos, en días naturales de la **zona horaria de la web**. `interval` aparece en las series y `previous` solo con `compare=1`.

## Parámetros comunes

Todas las rutas de estadísticas usan estos parámetros. Solo `site_id` es obligatorio.

| Parámetro    | Valores                                                                                                                    | Descripción                                                                                             |
| ------------ | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `site_id`    | Un `id` de `/sites`                                                                                                        | La web que consultas.                                                                                   |
| `period`     | `today`, `7d`, `30d` (por defecto), `month`, `lastMonth`, `90d`, `12m` (últimos 365 días), `year` (año en curso), `custom` | El periodo. Con `custom` hay que enviar `from` y `to`.                                                  |
| `from`, `to` | `AAAA-MM-DD`                                                                                                               | Primer y último día (incluidos), como mucho 400 días. Si los envías sin `period`, se entiende `custom`. |
| `compare`    | `0` (por defecto) o `1`                                                                                                    | Con `1` añade el periodo anterior de la misma duración.                                                 |
| Filtros      | Texto de 1 a 512 caracteres                                                                                                | Uno o varios de los filtros de la sección siguiente. Se combinan con «y».                               |

Si mandas un parámetro desconocido o un valor no válido, la API responde 400 con `validation_failed`.

### Filtros

Los mismos que el panel: `page`, `entry`, `source`, `utm_source`, `utm_medium`, `utm_campaign`, `country` (código ISO de 2 letras, como `ES`), `region`, `city`, `device` (`desktop`, `mobile`, `tablet` u `other`), `browser`, `os` y `event` (un evento personalizado; no puede empezar por `_`).

```bash
# Visitas desde España a /precios en los últimos 7 días
curl -s "https://api.limecount.com/api/v1/stats/overview?site_id=ID&period=7d&country=ES&page=/precios" \
  -H "Authorization: Bearer $LIMECOUNT_API_KEY"
```

## Endpoints

### GET /sites

Las webs a las que da acceso la clave: `id`, `domain`, `name`, `timezone`, `createdAt` y `verifiedAt` (`null` si aún no ha recibido datos). No lleva parámetros.

### GET /stats/overview

Los indicadores del periodo, los mismos que el resumen del panel. Cada uno lleva `kind`: `exact` si es un recuento, `estimated` si se calcula con reglas aproximadas (visitas, tiempo de lectura y rebote).

```json
{
  "data": {
    "kpis": {
      "pageviews": { "value": 12345, "previous": 11020, "kind": "exact" },
      "visits": { "value": 5873, "previous": 5410, "kind": "estimated" },
      "avgEngagementMs": { "value": 74000, "previous": 69500, "kind": "estimated" },
      "bounceRate": { "value": 0.41, "previous": 0.44, "kind": "estimated" },
      "events": { "value": 530, "previous": 498, "kind": "exact" }
    }
  },
  "meta": {
    "site_id": "665f1c2e9d1b2a0012a3b4c5",
    "from": "2026-10-06",
    "to": "2026-11-04",
    "timezone": "Europe/Madrid",
    "previous": { "from": "2026-09-06", "to": "2026-10-05" }
  }
}
```

`previous` dentro de cada indicador y `meta.previous` solo aparecen con `compare=1`. Según el modo de medición de la web puede haber además `sessions`, `visitors` y `measurement`; consulta [modos de medición](https://limecount.com/docs/modos-de-medicion.md). Con el filtro `event`, los indicadores de páginas son `null`.

### GET /stats/timeseries

Un punto por intervalo, con ceros incluidos. Parámetro propio:

| Parámetro  | Valores                                                 | Descripción                       |
| ---------- | ------------------------------------------------------- | --------------------------------- |
| `interval` | `hour`, `day` (por defecto), `week`, `month`, `quarter` | Tamaño de cada punto de la serie. |

- `hour`: un punto por hora. Admite como máximo **31 días** de rango. El resto de intervalos admiten el máximo que permite el panel (400 días).
- `week`: semanas ISO, de lunes a domingo.
- `month`: mes natural. `quarter`: trimestre natural (enero-marzo, abril-junio, julio-septiembre, octubre-diciembre).
- Todo se calcula en la zona horaria de la web. `date` es el inicio del intervalo: `AAAA-MM-DD`, o fecha y hora con desfase horario en `hour`.

```json
{
  "data": {
    "points": [
      { "date": "2026-11-02", "pageviews": 3010, "visits": 1432, "events": 128 },
      { "date": "2026-11-09", "pageviews": 3188, "visits": 1501, "events": 133 }
    ]
  },
  "meta": {
    "site_id": "665f1c2e9d1b2a0012a3b4c5",
    "from": "2026-11-02",
    "to": "2026-11-15",
    "timezone": "Europe/Madrid",
    "interval": "week"
  }
}
```

Con `compare=1`, `data.previous` trae la serie del periodo anterior con la misma forma.

### GET /stats/breakdown

Las filas de una dimensión, ordenadas de mayor a menor por su métrica principal. Parámetros propios:

| Parámetro   | Valores                      | Descripción                               |
| ----------- | ---------------------------- | ----------------------------------------- |
| `dimension` | Ver la lista de abajo        | Obligatorio. Dato por el que se desglosa. |
| `limit`     | De 1 a 1000 (10 por defecto) | Máximo de filas.                          |

```json
{
  "data": {
    "dimension": "countries",
    "rows": [
      { "value": "ES", "pageviews": 7020, "visits": 3310, "events": 301, "share": 0.563 },
      { "value": "MX", "pageviews": 1840, "visits": 905, "events": 72, "share": 0.154 }
    ],
    "total": 18,
    "omitted": 4
  },
  "meta": {
    "site_id": "665f1c2e9d1b2a0012a3b4c5",
    "from": "2026-10-06",
    "to": "2026-11-04",
    "timezone": "Europe/Madrid"
  }
}
```

`share` es la fracción (de 0 a 1) de la métrica principal. `total` son los valores distintos de la dimensión y `omitted` las filas que no se devuelven por tener menos de 3. En `regions` y `cities` las filas traen también `country` (y `region` en las ciudades) para distinguir lugares con el mismo nombre.

Dimensiones disponibles:

| Dimensión                                  | Qué desglosa                                                                           |
| ------------------------------------------ | -------------------------------------------------------------------------------------- |
| `pages`                                    | Páginas vistas                                                                         |
| `entries`                                  | Páginas de entrada                                                                     |
| `prev_pages`                               | Página anterior (de dónde llegan a una página; `(entry)` es entrada directa o externa) |
| `next_pages`                               | Página siguiente (adónde van desde una página). Exige el filtro `page`                 |
| `sources`                                  | Fuentes de tráfico (`(direct)` es tráfico directo)                                     |
| `utm_source`, `utm_medium`, `utm_campaign` | Campañas UTM                                                                           |
| `countries`, `regions`, `cities`           | Geografía (regiones y ciudades con menos de 5 visitas se agrupan en `(other)`)         |
| `devices`, `browsers`, `os`                | Dispositivos, navegadores y sistemas operativos                                        |
| `events`                                   | Eventos personalizados                                                                 |

### GET /stats/goals

Los objetivos de la web con sus conversiones y su tasa sobre las visitas. Acepta los parámetros comunes.

```json
{
  "data": {
    "visits": { "value": 5873, "previous": null },
    "totals": {
      "conversions": { "value": 212, "kind": "exact" },
      "rate": { "value": 0.036, "kind": "estimated" }
    },
    "goals": [
      {
        "id": "g1",
        "name": "Formulario enviado",
        "type": "event",
        "match": "contact_form",
        "conversions": { "value": 140, "kind": "exact" },
        "rate": { "value": 0.024, "kind": "estimated" }
      }
    ]
  },
  "meta": {
    "site_id": "665f1c2e9d1b2a0012a3b4c5",
    "from": "2026-10-06",
    "to": "2026-11-04",
    "timezone": "Europe/Madrid"
  }
}
```

`type` es `event` (un evento personalizado) o `path` (una página). Si la web no tiene objetivos, `goals` viene vacío. Más en [objetivos](https://limecount.com/docs/objetivos.md).

### GET /stats/funnels

Los embudos de la web con sus pasos y cuántas visitas llegan a cada uno. Si no tiene embudos propios, devuelve los dos predefinidos (compra y contacto) con `virtual: true`. Acepta los parámetros comunes.

```json
{
  "data": {
    "funnels": [
      {
        "funnel": {
          "id": "default:purchase",
          "name": "Compra",
          "isDefault": true,
          "virtual": true
        },
        "visits": { "value": 5873, "kind": "estimated" },
        "steps": [
          {
            "type": "event",
            "match": "view_pricing",
            "label": "Ver precios",
            "count": 900,
            "rateFromPrevious": null,
            "rateFromVisits": 0.153,
            "exceedsPrevious": false
          },
          {
            "type": "event",
            "match": "purchase",
            "label": "Compra",
            "count": 120,
            "rateFromPrevious": 0.133,
            "rateFromVisits": 0.02,
            "exceedsPrevious": false
          }
        ],
        "mixedModes": false
      }
    ]
  },
  "meta": {
    "site_id": "665f1c2e9d1b2a0012a3b4c5",
    "from": "2026-10-06",
    "to": "2026-11-04",
    "timezone": "Europe/Madrid"
  }
}
```

Más en [embudos](https://limecount.com/docs/embudos.md).

### GET /openapi.json

La especificación OpenAPI 3.1 de toda la API, pública y sin clave: [`https://api.limecount.com/api/v1/openapi.json`](https://api.limecount.com/api/v1/openapi.json). Impórtala en Postman, Insomnia o un generador de clientes.

## Límites

- **60 peticiones por minuto por clave** y **300 por minuto por organización**.
- Cada respuesta lleva `RateLimit-Limit` (peticiones permitidas en la ventana), `RateLimit-Remaining` (las que quedan) y `RateLimit-Reset` (segundos hasta que se reinicia).
- Al pasarte, la API responde `429` con `Retry-After` (segundos que hay que esperar). Espera ese tiempo y reintenta.
- Las respuestas no se cachean (`Cache-Control: no-store`): si necesitas la misma cifra muchas veces, guárdala tú.

## Errores

Los errores llevan el estado HTTP y un cuerpo JSON con un código estable:

```json
{
  "statusCode": 403,
  "code": "site_not_allowed",
  "message": "Esta clave no tiene acceso a esa web.",
  "error": { "code": "site_not_allowed", "message": "Esta clave no tiene acceso a esa web." }
}
```

| Estado | Código                 | Cuándo ocurre                                                                    |
| ------ | ---------------------- | -------------------------------------------------------------------------------- |
| 400    | `validation_failed`    | Parámetro desconocido o con un valor no válido.                                  |
| 400    | `invalid_range`        | Fechas imposibles, rango demasiado largo o más de 31 días con `interval=hour`.   |
| 400    | `page_filter_required` | `dimension=next_pages` sin el filtro `page`.                                     |
| 401    | `invalid_api_key`      | Falta la clave, está mal formada, está revocada o la organización se ha borrado. |
| 403    | `site_not_allowed`     | La web no está entre las de la clave o pertenece a otra organización.            |
| 403    | `insufficient_scope`   | La clave no tiene permiso para ese recurso.                                      |
| 404    | `site_not_found`       | La web no existe o se ha borrado.                                                |
| 429    | `rate_limited`         | Has superado el límite de peticiones. Mira `Retry-After`.                        |
| 503    | `stats_unavailable`    | Las estadísticas no están disponibles ahora mismo. Reintenta en unos segundos.   |

## Preguntas habituales

**¿Puedo sacar datos de cada visitante?** No. Limecount no los guarda de forma individual y la API solo devuelve agregados.

**¿Sigue funcionando si he cortado la recogida de datos?** Sí: la API lee datos ya recogidos. Si borras la organización, las claves dejan de funcionar.

**¿Y si borro una web?** Se quita de las claves que la tenían asignada.

**¿Se puede usar con una IA?** Sí. El [servidor MCP de Limecount](https://limecount.com/docs/usar-con-ia.md) usa esta misma API: añádele la clave en la cabecera `Authorization` y tu asistente podrá responder preguntas sobre tus estadísticas.

**¿Puedo exportar en CSV?** Desde el panel, con [Exportar CSV](https://limecount.com/docs/exportar.md). La API es la vía para automatizarlo.
