Ir al contenido

API de estadísticas — lleva tus números a donde ya trabajas

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.

  1. Entra en Configuración del negocio. En la tarjeta Integraciones, pulsa el botón Integraciones.

  2. Busca la tarjeta API de estadísticas y pulsa Crear clave.

  3. Pulsa Generar clave. La clave aparece en pantalla, empieza por zcst_ y es única de tu negocio.

  4. 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.

  5. 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 clave solo lee tu negocio. El negocio no se indica en la petición: se deduce de la propia clave. Una clave no puede consultar los datos de otra cuenta ni aunque quien la tenga lo intente a propósito, sencillamente porque no hay ningún parámetro donde pedirlo.
  • Es de servidor a servidor. No la pongas en una página web, ni en una app móvil, ni en el JavaScript de tu landing. Cualquiera que abra esa página puede ver la clave en el código y, con ella, leer todas tus métricas.
  • Guárdala donde se guardan las contraseñas. El almacén de credenciales de n8n, las variables de entorno de tu servidor, los custom values de GoHighLevel. No en un documento compartido ni en un export de flujo que luego circula por correo.
  • Ante la duda, regenera. Si la clave ha acabado en un log, en una captura de pantalla o en un chat, regenérala. Es gratis e instantáneo; lo único que cuesta es actualizarla en tu herramienta.
  • Una clave por negocio. Al generar una nueva se sustituye la anterior, así que si tienes varias integraciones apuntando a la misma clave, todas dejan de funcionar a la vez cuando la regeneras. Actualízalas todas.

La URL base es:

https://app.zerochats.com/api/statistics/v1

Todas las llamadas son GET y la clave viaja en la cabecera Authorization:

Ventana de terminal
curl -H "Authorization: Bearer zcst_tu_clave" \
https://app.zerochats.com/api/statistics/v1/overview

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.

EndpointAcepta fechasQué devuelve
/meNoEl negocio al que pertenece la clave y su zona horaria.
/overviewTotales históricos y reparto por etapa del embudo, separando entrantes y salientes.
/leads/dailyUna fila por día: leads recibidos y hasta dónde llegaron.
/leads/by-weekdayNoReparto histórico de leads por día de la semana.
/leads/by-hourNoReparto histórico de leads por hora del día.
/conversions/goalsLeads y clientes atribuidos a cada enlace de venta, por día.
/conversions/tagsLeads convertidos por etiqueta y día.
/conversions/contentsLeads y clientes atribuidos a cada contenido, por día.
/performanceRendimiento por publicación, anuncio u origen, con enlace al post.
/welcome-messagesMensajes 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.

{
"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 appstate
AbiertaOPEN_CONVERSATION
DescubriendoIN_PROGRESS
CualificandoQUALIFYING
PropuestaLEAD
AgendadoBOOKED
PagadoCLIENT
PospuestoPOSTPONED
No cualificadoNO_QUALIFY
PerdidoLOST
[{ "date": "2026-07-01", "total": 34, "qualified": 6, "booked": 2, "paid": 1 }]
CampoQué es
dateEl día, en formato YYYY-MM-DD.
totalLeads recibidos ese día.
qualifiedDe esos, los que están hoy en Propuesta.
bookedDe esos, los que están hoy en Agendado.
paidDe 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.

[{ "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/contents

Sección titulada «/conversions/goals, /conversions/tags y /conversions/contents»

Los 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.

Una 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 }
}
]
CampoQué es
kindmedia (una publicación o un anuncio), personalized (un origen que has definido tú), inbound, outbound.
idIdentificador de la fila. En media es el de la publicación en Instagram.
nameEl nombre del origen personalizado. Vacío en el resto de filas.
mediaIdIdentificador de la publicación. null si la fila no es una publicación.
mediaTypeSTORY, FEED, REELS o AD. null si la fila no es una publicación.
urlEnlace público al post en Instagram. null si no lo conocemos.
leadsLeads que tocaron esa publicación u origen.
answeredDe esos, los que llegaron a contestar.
qualifiedDe esos, los que llegaron a Propuesta, Agendado o Pagado.
bookedLos que agendaron.
paidLos que pagaron.
ratesEsos 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.

[{ "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ámetroFormatoQué es
startDayYYYY-MM-DDPrimer día, incluido.
endDayYYYY-MM-DDÚltimo día, incluido.
Ventana de terminal
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:

  • Los dos días son días de calendario en la zona horaria de tu negocio, no marcas de tiempo. Si tu negocio está en Madrid, el 1 de julio va de las 00:00 a las 23:59 de Madrid, aunque tú lances la consulta desde México. Ese es el motivo de que meta.timezone viaje en todas las respuestas.
  • O mandas los dos, o no mandas ninguno. Mandar solo uno da error en vez de suponer el otro.
  • Si no mandas ninguno, te damos los últimos 30 días, hoy incluido.
  • El máximo son 366 días. Un rango más largo da error; parte la consulta en trozos.

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:

CabeceraQué es
X-RateLimit-LimitPeticiones permitidas por minuto.
X-RateLimit-RemainingLas que te quedan en este minuto.
X-RateLimit-ResetCuándo se renueva el cupo, en segundos Unix.
Retry-AfterSegundos 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 HTTPerror.codeQué ha pasado
401unauthorizedFalta la cabecera, o la clave está mal escrita, revocada o ya no existe.
400invalid_requestFecha con formato incorrecto, solo uno de los dos días, orden invertido o rango de más de 366 días.
429rate_limitedHas superado las 60 peticiones en el minuto en curso.
500internal_errorAlgo 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.

Ejemplo práctico: informe semanal de contenido

Sección titulada «Ejemplo práctico: informe semanal de contenido»

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.

  1. 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.

  2. Llama a /performance con ese rango.

    Ventana de terminal
    curl -H "Authorization: Bearer zcst_tu_clave" \
    "https://app.zerochats.com/api/statistics/v1/performance?startDay=2026-08-17&endDay=2026-08-23"
  3. 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.

  4. Ordena por paid —o por rates.paid si te interesa más la eficiencia que el volumen— y quédate con las cinco primeras.

  5. 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 agosto
    1. 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/
  6. 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.

  • Guarda la zona horaria de 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.
  • Usa id como clave, no name. Los nombres de etiquetas, contenidos y enlaces de venta se pueden cambiar desde la app; los identificadores no.
  • Guarda la respuesta cruda además de tu tabla procesada, al menos mientras montas la integración. Cuando un número no cuadre, tener el JSON original ahorra medio día.
  • Pide rangos, no días sueltos. Una llamada de 30 días es más rápida y más barata que 30 llamadas de un día.
  • Refresca con cabeza. Los paneles de negocio se leen una vez al día; refrescar cada minuto no te da información nueva y te acerca al límite de peticiones.

Recibo 401 con una clave que acabo de copiar

Sección titulada «Recibo 401 con una clave que acabo de copiar»

Comprueba 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.

Los números no coinciden con lo que veo en Estadísticas

Sección titulada «Los números no coinciden con lo que veo en Estadísticas»

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 tengo

Sección titulada «/performance me devuelve más leads de los que tengo»

Es 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.

Algunas filas de /performance traen url a null

Sección titulada «Algunas filas de /performance traen url a null»

Significa 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.