Ir al contenido

API de lectura

Recupere sus estadísticas por programa: paneles internos, informes, exportaciones. Reservada a los planes Pro y Agencia, a la beta y a la prueba en curso.

Autenticación

Cree un token en Panel → Tokens API y páselo como Bearer:

curl -H "Authorization: Bearer SU_TOKEN" \
  "https://quietmetrics.dev/api/v1/stats/qm_pub_XXXXXXXXXXXXXXXXX/summary?period=30d"

Todas las rutas llevan el prefijo /api/v1/stats/{clave_publica_del_sitio}. Un token solo da acceso a los sitios de su cuenta; un sitio que no le pertenece responde 404 (sin fuga de existencia).

Un token cubre o bien todos sus sitios, actuales y futuros, o bien solo los sitios elegidos al crearlo: un sitio de su cuenta que no cubre responde 403. Deja de funcionar en la fecha de caducidad que muestra la lista de tokens.

Endpoints

Endpoint Contenido
GET /summary Indicadores del periodo, y los del periodo anterior de igual duración
GET /timeseries Serie temporal: un punto por día, o por hora cuando el periodo cabe en un solo día
GET /breakdown Clasificación de los valores de una dimensión ({label, value})
GET /realtime Visitantes activos en los últimos 5 minutos ({"visitors": N})

Parámetros comunes

period

Un preajuste del panel: today, yesterday, 7d, 30d (por defecto), month (mes en curso), last_month, 12m. Los periodos móviles terminan hoy, día incluido.

O un intervalo explícito, por ejemplo ?period=custom:2026-07-01..2026-07-31:

  • custom:AAAA-MM-DD..AAAA-MM-DD: del primer día al segundo, ambos incluidos, con 760 días como máximo. Los límites invertidos se reordenan. El intervalo puede terminar hoy, no después.

Las fechas son las de la zona horaria del sitio.

Un periodo no válido no produce ningún error: se sustituye por 30d. Así ocurre con un preajuste desconocido y con un intervalo mal formado (formato distinto de AAAA-MM-DD, fecha inexistente), demasiado largo o que termina en el futuro. Compruebe, pues, el periodo servido: todas las respuestas salvo realtime llevan period.from y period.to, y summary añade period.preset, que vale custom para un intervalo aceptado y 30d tras una sustitución.

El periodo anterior (previous en summary) tiene el mismo número de días y termina la víspera del primer día del periodo. Para month, no es por tanto el mes anterior completo, sino tantos días como lleva ya el mes en curso.

filters

filters admite los mismos filtros que los clics del panel, en formato dimension:valor, separados por ;:

?filters=country:FR;device:mobile
?filters=page:/precios
?filters=channel:search;browser:Firefox
  • Las dimensiones filtrables se indican en la tabla siguiente. Cualquier otra dimensión, incluidas entry y exit, responde 422 nombrándola: recibe un error en lugar de cifras sin filtrar.
  • Un solo valor por dimensión: cuando una dimensión aparece dos veces, prevalece la última (?filters=device:mobile;device:desktop solo conserva desktop). Dimensiones distintas se acumulan: solo cuentan los envíos que las cumplen todas.
  • filters solo acepta valores de 500 caracteres como máximo: más allá, el valor se descarta sin error y entonces no aparece en el filters de la respuesta.
  • El filters de summary lleva únicamente los filtros REALMENTE aplicados, y no la petición recibida: una dimensión que figura en él sí ha restringido las cifras que la acompañan.
  • Vocabulario cerrado: channel vale direct, search, social, referral, email o campaign; device vale desktop, mobile, tablet u other.
  • En cuanto se aplica un filtro, los indicadores se recalculan desde los eventos brutos, y tanto bounce_rate como avg_duration valen null.
  • En breakdown, un filtro sobre la dimensión clasificada se ignora para esa clasificación: ?dimension=country&filters=country:FR devuelve todos los países, para que el filtro no reduzca la clasificación a su propio valor.

dimension (breakdown)

dimension Qué cuenta value Utilizable en filters
page Páginas vistas sí
entry Entradas: sesiones que empezaron en esta página no
exit Salidas: sesiones que terminaron en esta página, calculadas la noche siguiente (el día en curso aún no tiene ninguna, salvo con filtro) no
referrer Sesiones, por dominio de referencia sí
channel Sesiones, por canal de adquisición sí
source Sesiones, por valor de utm_source sí
medium Sesiones, por valor de utm_medium sí
campaign Sesiones, por valor de utm_campaign sí
country Páginas vistas, por país (código ISO de dos letras) sí
region Páginas vistas, por región sí
browser Páginas vistas, por navegador sí
os Páginas vistas, por sistema operativo sí
device Páginas vistas, por tipo de dispositivo sí
lang Páginas vistas, por idioma sí
event Activaciones, por nombre de evento sí

Una dimensión ausente de esta tabla responde 422. label vale null para las visitas sin valor, por ejemplo sin dominio de referencia.

limit (breakdown): número de filas, 10 por defecto, acotado entre 1 y 1 000.

Granularidad de la serie temporal

GET /timeseries devuelve un punto por día en general, y un punto por hora en cuanto el período cabe en un solo día local (today, yesterday, o un intervalo custom: cuyos dos límites son iguales). El campo date toma entonces la forma AAAA-MM-DD HH:MM, en la hora local del sitio, en lugar de AAAA-MM-DD.

Un día local no siempre dura 24 horas. En los dos cambios de hora del año, la serie lleva por tanto 23, 24 o 25 puntos: no cuente con un número fijo.

  • Noche en que la hora se atrasa: 25 puntos. La hora local repetida (02:00 en París) da dos puntos consecutivos con el mismo date, cada uno con sus propias métricas. Este formato no lleva desfase horario: localice esos puntos por su posición en la lista, nunca por su etiqueta.
  • Noche en que la hora se adelanta: 23 puntos, la hora local omitida (02:00 en París) no existe. Ningún punto se desborda al día siguiente.

Los visitantes de un punto horario son los que estuvieron activos durante esa hora: una persona presente durante dos horas aparece en ambos, así que los puntos horarios no suman un total diario.

Ejemplos de respuesta

summary

GET /summary?period=30d:

{
    "site": "misitio.es",
    "period": { "preset": "30d", "from": "2026-06-17", "to": "2026-07-16" },
    "filters": {},
    "kpis": { "pageviews": 9873, "visitors": 4210, "sessions": 4630, "events": 312, "bounce_rate": 47.2, "avg_duration": 74 },
    "previous": { "pageviews": 9145, "visitors": 3877, "sessions": 4288, "events": 290, "bounce_rate": 49.1, "avg_duration": 71 }
}
kpis Contenido
pageviews Páginas vistas
visitors Visitantes únicos de cada día, sumados sobre el periodo: una persona que vuelve al día siguiente se cuenta dos veces
sessions Visitas
events Eventos medidos distintos de las páginas vistas
bounce_rate Tasa de rebote en porcentaje, de 0 a 100, redondeada a un decimal: proporción de sesiones con un solo envío medido. null sin ninguna sesión, o en cuanto se aplica un filtro
avg_duration Duración media de una sesión, en segundos enteros. null sin ninguna sesión, o en cuanto se aplica un filtro

previous lleva las mismas claves para el periodo anterior. La API no calcula ninguna variación: dedúzcala usted de los dos valores, tratando aparte un valor anterior nulo o null.

GET /summary?period=7d&filters=device:mobile:

{
    "site": "misitio.es",
    "period": { "preset": "7d", "from": "2026-07-10", "to": "2026-07-16" },
    "filters": { "device": "mobile" },
    "kpis": { "pageviews": 1204, "visitors": 611, "sessions": 655, "events": 38, "bounce_rate": null, "avg_duration": null },
    "previous": { "pageviews": 1139, "visitors": 590, "sessions": 622, "events": 41, "bounce_rate": null, "avg_duration": null }
}

timeseries

Un punto por día, días sin tráfico incluidos (valen cero).

GET /timeseries?period=custom:2026-07-14..2026-07-16:

{
    "period": { "from": "2026-07-14", "to": "2026-07-16" },
    "data": [
        { "date": "2026-07-14", "pageviews": 341, "visitors": 142, "sessions": 158, "events": 9 },
        { "date": "2026-07-15", "pageviews": 298, "visitors": 131, "sessions": 144, "events": 12 },
        { "date": "2026-07-16", "pageviews": 0, "visitors": 0, "sessions": 0, "events": 0 }
    ]
}

Cuando el periodo cabe en un solo día (today, yesterday, o un intervalo cuyos dos límites son iguales), la serie pasa a horas: un punto por hora del día local, de 00:00 a 23:00 en la hora local del sitio (23 o 25 puntos los días de cambio de hora, véase «Granularidad de la serie temporal»), y date toma la forma AAAA-MM-DD HH:MM. Los visitantes de un punto horario son los que estuvieron activos durante esa hora: una persona presente durante dos horas aparece en ambas, así que los puntos no se suman en un total diario.

GET /timeseries?period=custom:2026-07-16..2026-07-16, los tres primeros de los 24 puntos:

{
    "period": { "from": "2026-07-16", "to": "2026-07-16" },
    "data": [
        { "date": "2026-07-16 00:00", "pageviews": 4, "visitors": 3, "sessions": 3, "events": 0 },
        { "date": "2026-07-16 01:00", "pageviews": 1, "visitors": 1, "sessions": 1, "events": 0 },
        { "date": "2026-07-16 02:00", "pageviews": 0, "visitors": 0, "sessions": 0, "events": 0 }
    ]
}

breakdown

GET /breakdown?dimension=page&limit=3:

{
    "dimension": "page",
    "period": { "from": "2026-06-17", "to": "2026-07-16" },
    "data": [
        { "label": "/", "value": 3120 },
        { "label": "/precios", "value": 1284 },
        { "label": "/docs", "value": 977 }
    ],
    "capped": {
        "any": true,
        "dimensions": [
            { "dimension": "page", "days": 2, "days_repaired": 1, "folded_values": 1406, "cap": 2000 }
        ]
    }
}

Sin límite alcanzado en el periodo, capped vale {"any": false, "dimensions": []}.

realtime

GET /realtime:

{ "visitors": 3 }

Límite de cardinalidad (capped)

Cada sitio mide un número limitado de valores distintos por día y por dimensión. Más allá, los valores siguientes se cuentan juntos bajo una única etiqueta, ~autres: el detalle se agrupa, no se pierde ninguna visita y los totales siguen siendo exactos. breakdown anuncia este estado en la clave capped:

  • any siempre está presente y dimensions siempre es una lista: el esquema no varía según el estado del sitio, así que no hace falta ninguna bifurcación para leerlo.
  • dimension es la dimensión limitada, no la que usted ha pedido. entry y exit se agrupan por el límite page; source y medium se agrupan por el límite campaign.
  • days cuenta los días locales limitados del periodo; cap es el umbral más alto realmente aplicado (puede variar si se aplica un aumento a mitad del periodo).
  • folded_values: null NO significa cero. El número de valores agrupados solo se conoce tras la reconstrucción nocturna, que procesa un día como pronto en D+2. null significa «límite alcanzado, número aún desconocido»; 0 significa «día reconstruido, ningún valor agrupado». Mostrar 0 en lugar de null diría a sus usuarios que no pierden ningún detalle, lo cual es falso.
  • days_repaired indica en cuántos de los days días se conoce el recuento. Si days_repaired < days, folded_values es una suma parcial, como en el ejemplo anterior.

capped solo describe la dimensión pedida. También vale {"any": false, "dimensions": []} en cuanto un filtro se aplica a la clasificación: esta se lee entonces sobre los eventos brutos, donde nada se agrupa.

summary y timeseries no llevan esta clave: no leen ninguna dimensión y son insensibles al límite.

La exportación CSV del panel lleva el mismo indicador, pero en la cabecera HTTP X-Cardinality-Capped en vez de en el cuerpo del archivo: {"capped":false}, o {"capped":true,"dimension":"page","days":1,"days_repaired":1,"folded_values":2812,"cap":2000} acotado a la única dimensión exportada. El cuerpo del CSV sigue siendo dato máquina puro, sin fila de metadatos: ninguna fila sintética puede así falsear un total ni romper un analizador de número de columnas fijo.

Este límite puede aumentarse para su sitio: escriba al soporte.

Ejemplo PHP

$response = json_decode(file_get_contents(
    'https://quietmetrics.dev/api/v1/stats/qm_pub_XXXX/summary?period=7d',
    false,
    stream_context_create(['http' => ['header' => "Authorization: Bearer SU_TOKEN\r\nAccept: application/json"]])
), true);

echo $response['kpis']['visitors'];

Errores

El cuerpo de un error es un objeto JSON cuya clave message describe la causa.

Código Significado
401 Token ausente, inválido, revocado o caducado
403 El sitio es suyo, pero el token no lo cubre, o su plan no da acceso a la API (prueba caducada incluida)
403 Su cuenta está suspendida o pendiente de aprobación: la API permanece cerrada, sea cual sea el plan
404 Sitio desconocido, o perteneciente a otra cuenta
422 Dimensión de breakdown desconocida, o dimensión de filters sobre la que no se puede filtrar
429 Demasiadas peticiones: como máximo 120 por minuto por token, y 300 por minuto para toda la cuenta, todos los tokens juntos. La cabecera Retry-After indica la espera, en segundos

Buenas prácticas

  • Guarde en caché del lado cliente: consultar cada hora es más que suficiente. Ahora bien, un día cerrado no queda congelado para siempre: la reconstrucción nocturna vuelve sobre los días limitados y puede recalcular sus agregados hasta dos días después. Prevea, pues, una caché corta o una nueva consulta sobre los dos últimos días; más allá, los valores ya no se mueven.
  • Lea period en cada respuesta antes de mostrar una cifra: un periodo no válido no produce ningún error, se sustituye por 30d.
  • Un token por uso (uno para el panel interno, otro para el informe mensual): la revocación sigue siendo quirúrgica, y dos usos no se disputan el límite del mismo token.
  • Para una exportación puntual, cada vista del panel también se exporta en CSV desde el panel; no hace falta escribir código.

¿No encuentra su respuesta?

El soporte responde en días laborables.