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
entryyexit, responde422nombrá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:desktopsolo conservadesktop). Dimensiones distintas se acumulan: solo cuentan los envíos que las cumplen todas. filterssolo acepta valores de 500 caracteres como máximo: más allá, el valor se descarta sin error y entonces no aparece en elfiltersde la respuesta.- El
filtersdesummarylleva ú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:
channelvaledirect,search,social,referral,emailocampaign;devicevaledesktop,mobile,tabletuother. - En cuanto se aplica un filtro, los indicadores se recalculan desde los eventos brutos, y tanto
bounce_ratecomoavg_durationvalennull. - En
breakdown, un filtro sobre la dimensión clasificada se ignora para esa clasificación:?dimension=country&filters=country:FRdevuelve 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:
anysiempre está presente ydimensionssiempre es una lista: el esquema no varía según el estado del sitio, así que no hace falta ninguna bifurcación para leerlo.dimensiones la dimensión limitada, no la que usted ha pedido.entryyexitse agrupan por el límitepage;sourceymediumse agrupan por el límitecampaign.dayscuenta los días locales limitados del periodo;capes el umbral más alto realmente aplicado (puede variar si se aplica un aumento a mitad del periodo).folded_values: nullNO 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.nullsignifica «límite alcanzado, número aún desconocido»;0significa «día reconstruido, ningún valor agrupado». Mostrar0en lugar denulldiría a sus usuarios que no pierden ningún detalle, lo cual es falso.days_repairedindica en cuántos de losdaysdías se conoce el recuento. Sidays_repaired < days,folded_valueses 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
perioden cada respuesta antes de mostrar una cifra: un periodo no válido no produce ningún error, se sustituye por30d. - 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.