Looker Studio o Power BI
Un panel que mezcla tus leads de ZeroChats con la inversión publicitaria y calcula el coste real por cliente.
La API de estadísticas te deja leer los números de tu negocio en ZeroChats desde fuera de la plataforma. Generas una clave, la pones en la herramienta que ya usas y esa herramienta consulta tus métricas directamente: los mismos leads, las mismas conversiones y el mismo rendimiento por publicación que ves en la sección Estadísticas.
Es la respuesta a un problema concreto: tus datos de ZeroChats viven en ZeroChats y el resto de tus datos —inversión en anuncios, ventas, facturación— viven en otro sitio. Con la API los juntas en el mismo panel sin exportar un CSV cada lunes.
Looker Studio o Power BI
Un panel que mezcla tus leads de ZeroChats con la inversión publicitaria y calcula el coste real por cliente.
Google Sheets
Una hoja que se actualiza sola con los leads del mes y las conversiones por enlace de venta.
n8n, Make o Zapier
Un flujo que cada lunes lee la semana anterior y publica el resumen en Slack o te lo manda por email.
Tu propio backend
Cualquier servicio que sepa hacer una petición HTTP con una cabecera puede consumirla.
La API es de solo lectura. No crea leads, no manda mensajes y no cambia nada dentro de ZeroChats.
Entra en Configuración del negocio. En la tarjeta Integraciones, pulsa el botón Integraciones.
Busca la tarjeta API de estadísticas y pulsa Crear clave.
Pulsa Generar clave. La clave aparece en pantalla, empieza por zcst_ y es única de tu negocio.
Cópiala en ese momento. Es la única vez que se muestra completa. A partir de ahí solo verás una versión abreviada, tipo zcst_ab12…7yz9, suficiente para reconocerla pero no para usarla.
Guárdala en tu herramienta y haz la primera llamada para comprobar que responde.
Una vez creada, la tarjeta te dice si la clave se ha usado alguna vez y cuándo fue la última llamada. Es la forma rápida de comprobar que la integración del otro lado está realmente funcionando: si dice sin llamadas todavía, tu herramienta no ha llegado a conectar.
Desde ese mismo diálogo puedes Regenerar la clave (crea una nueva y anula la anterior al instante) o Revocarla (la borra; a partir de ese momento toda llamada recibe un error de autorización).
Léete esta sección entera antes de pegar la clave en ningún sitio.
La URL base es:
https://app.zerochats.com/api/statistics/v1Todas las llamadas son GET y la clave viaja en la cabecera Authorization:
curl -H "Authorization: Bearer zcst_tu_clave" \ https://app.zerochats.com/api/statistics/v1/overviewAñade un nodo HTTP Request:
GET, URL https://app.zerochats.com/api/statistics/v1/overview.Authorization, valor Bearer zcst_tu_clave.Guarda la credencial una vez y reutilízala en todos los nodos que llamen a la API.
const respuesta = await fetch( 'https://app.zerochats.com/api/statistics/v1/overview', { headers: { Authorization: `Bearer ${process.env.ZEROCHATS_STATS_KEY}` } });
const { success, data, meta } = await respuesta.json();Si tu herramienta no te deja tocar la cabecera Authorization, también aceptamos la clave en x-api-key con el mismo valor, sin el Bearer delante.
Todas las respuestas tienen la misma forma. Si la llamada va bien:
{ "success": true, "data": {}, "meta": { "startDay": "2026-07-01", "endDay": "2026-07-30", "timezone": "Europe/Madrid" }}data cambia según el endpoint. meta te dice siempre qué periodo y qué zona horaria han producido esos números, así que no necesitas recordar lo que pediste para interpretar lo que recibes.
Si algo falla:
{ "success": false, "error": { "code": "invalid_request", "message": "startDay: must be a date in YYYY-MM-DD format" }}Toma tus decisiones mirando error.code, que es estable. El message está pensado para que lo leas tú mientras montas la integración y puede cambiar de redacción.
| Endpoint | Acepta fechas | Qué devuelve |
|---|---|---|
/me | No | El negocio al que pertenece la clave y su zona horaria. |
/overview | Sí | Totales históricos y reparto por etapa del embudo, separando entrantes y salientes. |
/leads/daily | Sí | Una fila por día: leads recibidos y hasta dónde llegaron. |
/leads/by-weekday | No | Reparto histórico de leads por día de la semana. |
/leads/by-hour | No | Reparto histórico de leads por hora del día. |
/conversions/goals | Sí | Leads y clientes atribuidos a cada enlace de venta, por día. |
/conversions/tags | Sí | Leads convertidos por etiqueta y día. |
/conversions/contents | Sí | Leads y clientes atribuidos a cada contenido, por día. |
/performance | Sí | Rendimiento por publicación, anuncio u origen, con enlace al post. |
/welcome-messages | Sí | Mensajes de bienvenida enviados, respondidos y tasa de respuesta, por día. |
Los tres endpoints que no aceptan fechas (/me, /leads/by-weekday y /leads/by-hour) cubren toda la historia del negocio. Si les mandas startDay y endDay no da error: simplemente los ignora, y lo notas porque meta viene sin fechas.
/overview{ "lifetime": { "totalLeads": 1840, "positiveLeads": 213 }, "funnel": { "inbound": [ { "state": "OPEN_CONVERSATION", "count": 48 }, { "state": "LEAD", "count": 21 }, { "state": "CLIENT", "count": 7 } ], "outbound": [{ "state": "IN_PROGRESS", "count": 12 }] }}lifetime es histórico y no depende del rango que pidas: totalLeads son todos tus leads y positiveLeads los que están en Propuesta, Agendado o Pagado.
funnel sí cubre el rango, y separa los leads entrantes (te escribieron ellos) de los salientes (empezaste tú la conversación). Van aparte a propósito: son dos formas distintas de captar y mezclarlas esconde cuál de las dos convierte.
Cada etapa llega por su nombre en inglés, igual que en el Webhook personalizado y por el mismo motivo: los nombres son iguales en todas las cuentas trabajes en el idioma que trabajes, así que un filtro construido sobre ellos no se rompe.
| Etapa en la app | state |
|---|---|
| Abierta | OPEN_CONVERSATION |
| Descubriendo | IN_PROGRESS |
| Cualificando | QUALIFYING |
| Propuesta | LEAD |
| Agendado | BOOKED |
| Pagado | CLIENT |
| Pospuesto | POSTPONED |
| No cualificado | NO_QUALIFY |
| Perdido | LOST |
/leads/daily[{ "date": "2026-07-01", "total": 34, "qualified": 6, "booked": 2, "paid": 1 }]| Campo | Qué es |
|---|---|
date | El día, en formato YYYY-MM-DD. |
total | Leads recibidos ese día. |
qualified | De esos, los que están hoy en Propuesta. |
booked | De esos, los que están hoy en Agendado. |
paid | De esos, los que están hoy en Pagado. |
Los días sin actividad vienen igualmente, con ceros, para que puedas pintar la serie tal cual sin rellenar huecos.
/leads/by-weekday y /leads/by-hour[{ "dayOfWeek": 0, "day": "MONDAY", "total": 240, "qualified": 31, "booked": 14 }][{ "hour": 9, "total": 88, "qualified": 12, "booked": 5 }]dayOfWeek va de 0 (lunes) a 6 (domingo) y day te da el nombre para que no tengas que recordar la convención. hour va de 0 a 23. Ambos vienen siempre completos: los siete días y las veinticuatro horas, aunque alguna esté a cero.
Aquí qualified son los leads en Propuesta y booked agrupa Agendado y Pagado. No intentes cuadrar estos dos endpoints fila a fila con /leads/daily: responden a otra pregunta —a qué hora y qué día te escribe la gente— y no al recorrido de una cohorte.
/conversions/goals, /conversions/tags y /conversions/contentsLos tres devuelven la misma forma, una fila por entidad y día:
[ { "date": "2026-07-14", "id": "9f1c8a4e-2b77-4d0a-9d1c-3f2b6a5c7e10", "name": "Sesión de valoración", "leads": 4, "clients": 1 }]name te llega ya resuelto —el nombre del enlace de venta, de la etiqueta o del contenido— para que no tengas que hacer una segunda consulta ni mantener tu propia tabla de equivalencias.
En /conversions/tags, clients vale lo mismo que leads: una etiqueta está puesta o no lo está, no hay una distinción entre lead y cliente que reportar. En enlaces de venta y contenidos sí son dos números distintos.
/performanceUna fila por publicación, anuncio u origen de los leads del periodo:
[ { "kind": "media", "id": "17900000000000000", "name": null, "mediaId": "17900000000000000", "mediaType": "REELS", "url": "https://www.instagram.com/reel/CxYzAbCdEfG/", "leads": 120, "answered": 86, "qualified": 24, "booked": 9, "paid": 4, "rates": { "answered": 71.67, "qualified": 20, "booked": 7.5, "paid": 3.33 } }]| Campo | Qué es |
|---|---|
kind | media (una publicación o un anuncio), personalized (un origen que has definido tú), inbound, outbound. |
id | Identificador de la fila. En media es el de la publicación en Instagram. |
name | El nombre del origen personalizado. Vacío en el resto de filas. |
mediaId | Identificador de la publicación. null si la fila no es una publicación. |
mediaType | STORY, FEED, REELS o AD. null si la fila no es una publicación. |
url | Enlace público al post en Instagram. null si no lo conocemos. |
leads | Leads que tocaron esa publicación u origen. |
answered | De esos, los que llegaron a contestar. |
qualified | De esos, los que llegaron a Propuesta, Agendado o Pagado. |
booked | Los que agendaron. |
paid | Los que pagaron. |
rates | Esos mismos cuatro números en porcentaje sobre leads, con dos decimales. |
El url es la pieza que convierte un informe en algo accionable: cada fila de tu panel puede enlazar al post real, así que quien lo lee pasa del número al contenido de un clic. Es un enlace normal de Instagram, estable y público — no caduca.
Los contadores tampoco son excluyentes entre sí: answered incluye a los qualified, y qualified incluye a los que agendaron y a los que pagaron. Se leen como un embudo, de más ancho a más estrecho.
/welcome-messages[{ "date": "2026-07-01", "sent": 40, "responded": 11, "responseRate": 27.5 }]responseRate es el porcentaje de sent con dos decimales, y vale 0 los días en que no enviaste nada.
Los endpoints marcados como «Acepta fechas» reciben dos parámetros:
| Parámetro | Formato | Qué es |
|---|---|---|
startDay | YYYY-MM-DD | Primer día, incluido. |
endDay | YYYY-MM-DD | Último día, incluido. |
curl -H "Authorization: Bearer zcst_tu_clave" \ "https://app.zerochats.com/api/statistics/v1/leads/daily?startDay=2026-07-01&endDay=2026-07-31"Las reglas son cuatro:
meta.timezone viaje en todas las respuestas.Un ejemplo de las dos primeras reglas juntas: pedir startDay=2026-07-01&endDay=2026-07-31 te da julio entero, los 31 días, con la primera y la última fila incluidas. Pedir startDay=2026-07-31&endDay=2026-07-01 da error, porque el inicio tiene que ir antes que el final. Y pedir solo startDay=2026-07-01 también da error, con el mensaje startDay and endDay must be provided together.
Puedes hacer 60 peticiones por minuto. Es de sobra para un panel que se refresca cada hora o para un informe diario; solo te lo encontrarás si montas un bucle que recorre muchos días de uno en uno.
Cada respuesta te dice cómo vas:
| Cabecera | Qué es |
|---|---|
X-RateLimit-Limit | Peticiones permitidas por minuto. |
X-RateLimit-Remaining | Las que te quedan en este minuto. |
X-RateLimit-Reset | Cuándo se renueva el cupo, en segundos Unix. |
Retry-After | Segundos que tienes que esperar. Solo cuando te pasas. |
Cuando te pasas recibes un 429 con error.code a rate_limited. La forma correcta de tratarlo es esperar los segundos que dice Retry-After y reintentar: cuando vuelvas tendrás el cupo entero. Reintentar antes solo consume intentos.
Si necesitas muchos días, pide un rango amplio en una sola llamada en vez de un día por petición: los endpoints por día ya te devuelven la serie completa.
| Código HTTP | error.code | Qué ha pasado |
|---|---|---|
| 401 | unauthorized | Falta la cabecera, o la clave está mal escrita, revocada o ya no existe. |
| 400 | invalid_request | Fecha con formato incorrecto, solo uno de los dos días, orden invertido o rango de más de 366 días. |
| 429 | rate_limited | Has superado las 60 peticiones en el minuto en curso. |
| 500 | internal_error | Algo ha fallado por nuestra parte. Reintenta con espera y, si sigue, abre un ticket. |
Un 401 después de que todo funcionara suele significar una de dos cosas: alguien ha regenerado la clave desde la app, o la ha revocado. Entra en el diálogo y comprueba la fecha de creación.
Un caso real y completo: cada lunes quieres saber qué publicaciones te trajeron clientes la semana pasada, con enlace a cada post para poder repasarlas.
Calcula el rango. Del lunes anterior al domingo anterior, en formato YYYY-MM-DD. Por ejemplo startDay=2026-08-17 y endDay=2026-08-23.
Llama a /performance con ese rango.
curl -H "Authorization: Bearer zcst_tu_clave" \ "https://app.zerochats.com/api/statistics/v1/performance?startDay=2026-08-17&endDay=2026-08-23"Quédate con las filas kind: "media". Son las publicaciones y los anuncios; inbound y outbound son las bolsas generales y no te sirven para este informe.
Ordena por paid —o por rates.paid si te interesa más la eficiencia que el volumen— y quédate con las cinco primeras.
Monta el mensaje usando url. Cada línea lleva el enlace al post, así que quien lo lee puede abrirlo sin buscarlo.
Top contenidos del 17 al 23 de agosto1. REELS — 120 leads · 4 clientes (3,33 %) → https://www.instagram.com/reel/CxYzAbCdEfG/2. FEED — 86 leads · 3 clientes (3,49 %) → https://www.instagram.com/p/CxAbCdEfGhI/Publícalo donde lo vayas a leer: un canal de Slack, un email o una fila nueva en una hoja de cálculo.
Si quieres añadir contexto de negocio a ese mismo informe, llama también a /leads/daily con el mismo rango para el volumen total de la semana, y a /conversions/goals para saber qué enlace de venta cerró esos clientes.
meta junto a los datos si los almacenas. Un número sin saber en qué calendario está medido no se puede comparar con otro más adelante.id como clave, no name. Los nombres de etiquetas, contenidos y enlaces de venta se pueden cambiar desde la app; los identificadores no.401 con una clave que acabo de copiarComprueba que has copiado la clave entera, incluido el prefijo zcst_, y que no se ha colado un espacio ni un salto de línea al pegarla. Comprueba también que la cabecera es Authorization: Bearer <clave> con el Bearer delante, o x-api-key: <clave> sin él — pero no una mezcla de las dos.
Casi siempre es el rango. Repasa que estés pidiendo los mismos días —recuerda que ambos extremos van incluidos— y mira meta.timezone: si tu herramienta calcula las fechas en su propia zona horaria y tu negocio está en otra, estarás comparando periodos ligeramente distintos.
En /leads/by-weekday y /leads/by-hour hay además una diferencia de criterio deliberada, explicada más arriba: booked incluye ahí también a los que ya han pagado.
/performance me devuelve más leads de los que tengoEs lo esperado y está explicado más arriba: un lead que ha tocado varias publicaciones cuenta en todas. Para el total del negocio, usa /overview o /leads/daily.
/performance traen url a nullSignifica que no tenemos el enlace público de esa publicación —suele pasar con contenidos antiguos o eliminados—. Muestra la fila igual usando mediaType como etiqueta, pero sin enlace.