Documentación API — Growth54
https://api.growth54.com
Growth54 es una plataforma de control empresarial multiempresa orientada a automatización, contenido y crecimiento asistido por IA. Su API REST permite que el panel web, los agentes de inteligencia artificial y los workflows de n8n lean y escriban datos de cada empresa registrada.
La API tiene cuatro capas de acceso según quién llama: usuarios del panel (Sanctum), administradores master, automatizaciones de n8n (solo lectura) y agentes de IA (escritura de resultados). Esta separación permite que cada integración tenga exactamente los permisos que necesita.
Esta guía cubre todos los endpoints disponibles, explica el propósito de cada módulo y documenta el brief técnico para construir los agentes n8n del módulo RRSS.
Convenciones
Reglas que aplican a todas las llamadas, independientemente del módulo o tipo de acceso. Leer esto antes de integrar cualquier endpoint.
N8N_API_TOKEN o AGENT_API_TOKEN están vacíos en el .env, esas rutas aceptan llamadas sin token. Esto facilita las pruebas iniciales. Para pasar a producción: definir ambas variables en el .env del backend y reiniciar los contenedores Docker.
Headers obligatorios en toda llamada
Accept: application/json
Content-Type: application/json
Códigos de error comunes
- 401 — Token no enviado o inválido.
- 403 — Sin permiso para esa acción.
- 422 — Datos incorrectos o empresa activa no asignada.
- 404 — Recurso no existe o no pertenece a tu empresa.
Tipos de acceso
La API distingue cuatro tipos de caller, cada uno con su propio prefijo de ruta y mecanismo de autenticación. La separación es intencional: un agente de IA solo puede escribir datos de resultados, no acceder a la configuración del usuario; un workflow de n8n solo puede leer, no modificar.
| Tipo | Prefijo | Quién lo usa |
|---|---|---|
| Usuario del panel | /api/auth/* | Frontend — token Sanctum |
| Admin master | /api/admin/* | Administrador de plataforma |
| n8n / automatizaciones | /api/n8n/* | Workflows — solo lectura |
| Agentes de IA | /api/agent/* | Agentes — escritura de datos |
| Público | /api/public/* | Sin token, con rate limit |
422. Se cambia con POST /api/auth/switch-company pasando el company_id deseado. La empresa activa es de cada sesión, no de la cuenta: dos tokens del mismo usuario pueden estar en empresas distintas al mismo tiempo New · 6-ago.
Registro público
sin token · rate 5/minEstos endpoints no requieren autenticación. Están diseñados para recibir leads desde formularios externos — una landing page en WordPress, un anuncio con formulario, etc. El rate limiting de 5 solicitudes por minuto evita que se use como vector de spam. Al registrar un cliente, el sistema crea el usuario en estado pendiente y lo asigna al flujo de onboarding.
Registra un cliente desde formulario externo (WordPress, landing page, etc.).
curl -s -X POST "https://api.growth54.com/api/public/register-client" \
-H "Accept: application/json" -H "Content-Type: application/json" \
-d '{"name":"Juan García","email":"juan@empresa.com"}' | jq
Verifica que la app, base de datos y caché respondan.
curl -s "https://api.growth54.com/api/health" | jq
Login de usuario
SanctumEl flujo estándar para el panel web. El login devuelve un token Bearer de Laravel Sanctum que se incluye en todas las llamadas posteriores. El token no expira automáticamente — se invalida llamando a logout. Después del login, el primer paso es llamar a switch-company para activar la empresa con la que se quiere trabajar.
curl -s -X POST "https://api.growth54.com/api/auth/login" \
-H "Accept: application/json" -H "Content-Type: application/json" \
-d '{"email":"user@demo.com","password":"secret"}' | jq
Datos del usuario autenticado.
Cambia la empresa activa. Necesario antes de usar módulos.
curl -s -X POST "https://api.growth54.com/api/auth/switch-company" \
-H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
-d '{"company_id": 1}' | jq
switch-company, todas las demás pasaban a ver esa empresa sin haber tocado nada. Ahora vive en el token: cada sesión (pestaña, dispositivo, script) mantiene su propia empresa activa y switch-company solo afecta al token con el que se llamó. La columna de users queda como recuerdo de la última empresa usada y es con la que arranca cada login nuevo. Impacto para integraciones: un script que hacía login una vez y esperaba que otro proceso le cambiara la empresa ya no funciona — cada token debe llamar a su propio switch-company. Los tokens creados antes del cambio se sellan solos en la primera llamada a /api/auth/me.Invalida el token actual.
Admin master
is_master = truecurl -s -X POST "https://api.growth54.com/api/admin/login" \
-H "Content-Type: application/json" \
-d '{"email":"admin@demo.com","password":"secret"}' | jq
GET lista · POST crear · GET/{id} · PUT actualizar · DELETE
Al crear con owner_user_id asigna rol owner automáticamente.
Onboarding
auth:sanctumEl proceso que sigue un usuario recién registrado. Tiene dos caminos: crear una empresa nueva (y quedar como owner) o unirse a una empresa existente usando un código de invitación (y quedar como collaborator). Los códigos de invitación los generan los owners o admins de la empresa, y se pueden limitar en número de usos y fecha de expiración.
Crea empresa y asigna al usuario como owner.
curl -s -X POST "https://api.growth54.com/api/onboarding/companies" \
-H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
-d '{"name":"Mi Empresa","industry":"Legal","website":"https://mi.com"}' | jq
Unirse con código de invitación. Rol: collaborator.
-d '{"code":"ABC123"}'
Genera código de invitación. Solo owner o admin.
-d '{"max_uses": 10, "expires_in_days": 30}'
Keywords
auth:sanctumLas keywords son el punto de partida del motor de contenido SEO. Cada empresa mantiene un banco de palabras clave clasificadas por dificultad, intención de búsqueda y fuente de origen. Los agentes de IA usan este banco para generar artículos relevantes y posicionar el sitio de la empresa. Las keywords pueden agregarse manualmente desde el panel o en bulk desde un workflow de n8n que haya hecho keyword research.
/api/agent/keywords/bulk → el usuario las revisa en el panel → aprueba las que le interesan → el agente generador de artículos las convierte en contenido.Lista keywords de la empresa activa.
Totales por dificultad, intención, etc.
Crea keyword manual. Valores: dificultad_nivel: baja/media/alta · intencion: informacional/transaccional/comercial/local
-d '{"keyword":"abogado laboral","dificultad_nivel":"media","intencion":"comercial","fuente":"Google"}'
Dispara el agente de keyword research para la empresa activa.
Dispara generación de artículo para una keyword específica.
Artículos
auth:sanctumLos artículos son el contenido SEO generado por los agentes a partir de las keywords. Cada artículo parte de una keyword, el agente genera el HTML completo y lo guarda como borrador. Desde el panel, el equipo lo revisa, edita si necesita y lo marca como revisado. Luego puede publicarse directamente en WordPress mediante la integración CMS. El listado no incluye el HTML para no sobrecargar la respuesta — se obtiene con el endpoint de detalle.
Lista (sin HTML). estado: borrador/revisado/publicado
Detalle completo con HTML.
Marca como publicado y asigna fecha.
Auditorías
auth:sanctumLas auditorías son reportes de análisis completos generados por agentes. A diferencia de la auditoría inicial (que es única por empresa), aquí puede haber múltiples auditorías acumuladas — por ejemplo, un análisis competitivo mensual, una revisión técnica post-migración, etc. Cada auditoría contiene un reporte HTML completo que se muestra en el panel.
Lista (sin HTML).
Detalle con HTML del reporte.
Auditoría inicial
auth:sanctumCada empresa tiene exactamente una auditoría inicial: el diagnóstico de punto de partida que se hace cuando el cliente comienza a usar Growth54. Analiza el sitio web, evalúa el SEO técnico, identifica oportunidades y genera un score. Este score sirve como línea base para medir el progreso en el tiempo. El agente n8n la genera automáticamente; si se vuelve a ejecutar, sobreescribe la anterior (upsert).
Dispara el workflow n8n. El agente guarda el resultado vía /api/agent/auditoria-inicial.
Páginas / URLs
auth:sanctumRegistro de las URLs del sitio web de la empresa. Permite hacer seguimiento del posicionamiento por página individual y auditar cada URL de forma independiente. Las páginas se pueden agregar manualmente o descubrirse automáticamente mediante un crawl del sitio. Una vez registradas, el agente de auditoría SEO puede analizar cada URL y devolver recomendaciones específicas.
Registra nueva URL del sitio.
Descubrimiento automático de páginas.
Dispara auditoría SEO para una URL específica.
Productos y servicios
auth:sanctumCatálogo de lo que vende la empresa. Los agentes de IA leen este catálogo antes de generar contenido para que los artículos, posts e ideas mencionen los productos reales del negocio y no generen texto genérico. Por ejemplo, un agente de RRSS que sabe que la empresa ofrece "auditoría gratuita de 30 minutos" puede incluirlo como CTA en los posts. Sin este catálogo, el agente genera contenido sin contexto comercial.
Competidores
auth:sanctumCRUD manual de competidores de la empresa. Cada competidor tiene un campo origen que puede ser manual (creado desde el panel) o agente (enviado por un workflow externo). El panel muestra ambos tipos en la misma tabla.
Carga masiva de competidores desde un agente externo (n8n u otro workflow). Las URLs inválidas se descartan silenciosamente sin romper el batch. Soporta modo upsert (default) o insert_only con skip_existing: true. Los registros se guardan con origen = agente.
{
"empresa_id": 1,
"competitors": [
{ "url": "https://competidor.com", "name": "Competidor" },
{ "url": "otro.com" }
],
"skip_existing": false
}
CMS Integrations
auth:sanctumConexiones con el sitio web de la empresa para publicar contenido directamente desde Growth54. Por ahora soporta WordPress vía su API REST. El flujo es: el equipo guarda las credenciales WordPress de la empresa (URL, usuario, application password) → el agente generador de artículos las usa para hacer un POST directo al CMS y publicar el artículo sin intervención manual. Las credenciales siempre requieren token aunque el servidor esté en modo provisional.
Credenciales CMS para que el agente pueda publicar. Siempre requiere token aunque el servidor esté en modo provisional.
Agent Endpoints
auth:sanctumRegistro global de los webhooks de n8n disponibles en la plataforma. Cada agente tiene una key única (por ejemplo rrss_strategist) y una URL de webhook. Cuando el usuario hace clic en "Ejecutar agente" en el panel, el backend busca la key correspondiente en esta tabla, obtiene el webhook y lo llama. Si el webhook no está registrado o el agente está inactivo, la llamada falla con 422 y el panel muestra un mensaje indicando que hay que configurarlo en esta sección. Esto permite cambiar o actualizar los workflows de n8n sin tocar el código del backend.
rrss_schedule_alarm New · 29-jul — Alarma de publicación: la plataforma la llama al programar un post. Se siembra con AgentEndpointSeeder apuntando a schedule-alarm-g54. Ver RRSS — Posts. Las keys rrss_community_ai y rrss_sales_ai ya no son informativas: si están activas y con webhook, sus tarjetas del pipeline dejan de mostrarse como "Próximamente".Activa o desactiva el agente.
Prueba la conectividad con el webhook.
rrss_distribution_ai (Distribution AI, módulo RRSS, webhook distribution-publish-g54). Publica los posts aprobados en Facebook e Instagram vía Graph API. Tiene trigger horario propio en n8n, pero al estar registrado la plataforma puede notificarlo para publicar de inmediato al aprobar un post. Se siembra con php artisan db:seed --class=AgentEndpointSeeder (idempotente).RRSS — Canales sociales
auth:sanctumEl módulo RRSS gestiona toda la operación de redes sociales de la empresa: desde la estrategia hasta la publicación y las métricas. Esta primera sección configura qué plataformas usa la empresa y con qué cuenta. Es el dato de partida que leen todos los agentes RRSS: sin canales configurados, los agentes no saben en qué plataformas generar contenido ni qué handle mencionar.
Upsert — actualiza el estado completo de los canales. Cada canal puede llevar image_config: la configuración de imagen que el cliente elige por red (no es igual para Instagram que para Facebook) y que después leen los agentes generadores. Todas sus claves son opcionales; se validan contra listas fijas y se limpia lo desconocido.
-d '{"channels":[{"platform":"instagram","active":true,"handle":"@miempresa"},{"platform":"linkedin","active":true}]}'
// Con image_config por canal:
{"channels":[{
"platform":"Instagram","is_active":true,"handle":"@miempresa","publishing_mode":"manual",
"image_config":{
"img_estilo":"realista", // realista | humanos | 3d | animado | minimalista | artistico
"img_formato":"cuadrada", // cuadrada (1:1) | vertical (4:5) | horizontal (16:9)
"img_texto_modo":"sin_texto" // sin_texto | solo_titulo | titulo_subtitulo | titulo_sub_detalles | libre
}
}]}
RRSS — Estrategia
auth:sanctumUna empresa tiene exactamente una estrategia RRSS. El PUT es upsert.
data: null si no existe aún.
New 17-jul: acepta y guarda fecha_inicio, fecha_fin (horizonte temporal de la estrategia) y direccion_deseada (a dónde llevar la marca). Antes estos campos no existían y se descartaban. Se mantiene una estrategia por empresa (se sobrescribe al regenerar).
-d '{
"fecha_inicio":"2026-07-01","fecha_fin":"2026-10-01",
"direccion_deseada":"Posicionar la marca como referente regional",
"objetivo":"Generar leads calificados","audiencia":"Dueños PYME 28-45 LATAM",
"tono":"Profesional pero cercano","frecuencia":"6 posts semanales",
"cta_principal":"Agendar llamada gratuita","embudo":"RRSS → Website → Lead → CRM",
"plataformas":["Facebook","Instagram","LinkedIn"],
"pilares":["Educación","Casos de éxito","Detrás de cámaras"],
"kpis":["Alcance orgánico","Engagement","Leads generados"]
}'
New 29-jul (B-17): se agregaron 6 campos que el Strategist AI ya generaba y que el guardado descartaba en silencio (mismo caso que fecha_inicio/fecha_fin/direccion_deseada el 17-jul): resumen_estrategico (texto), keyword_strategy, mapa_preguntas_aeo, faqs, clusters y plan_publicacion. Los 5 últimos se guardan como JSON: se prefiere mandar arrays, pero también se acepta texto plano. Aplican igual a POST /api/agent/rrss/estrategia, que es la vía que usa el agente.
-d '{
"resumen_estrategico":"Posicionar la marca en logística regional",
"keyword_strategy":[{"kw":"exportar a colombia","volumen":1200,"intencion":"comercial"}],
"mapa_preguntas_aeo":["¿Cuánto cuesta exportar a Colombia?"],
"faqs":[{"q":"¿Hacen envíos el mismo día?","a":"Sí, según zona"}],
"clusters":["logistica","aduanas"],
"plan_publicacion":[{"semana":1,"posts":3,"pilar":"Educación"}]
}'
RRSS — Ideas
auth:sanctumEl banco de ideas es el primer paso del pipeline de contenido social. Una idea es simplemente un tema propuesto: "5 errores que cometen las PYMEs en Instagram". Puede crearla manualmente el equipo, o puede generarlas el agente Content AI en bulk. El equipo revisa el banco y aprueba las que le parecen buenas — las aprobadas son las que el agente luego convierte en posts completos. Las descartadas quedan registradas para no proponer el mismo tema de nuevo.
Filtros: ?status=aprobado · ?platform=Instagram
-d '{"topic":"5 errores de PYMEs en Instagram","platform":"Instagram","keyword":"marketing PYME"}'
{"status":"aprobado"}
Edita una idea ya creada. Pensado para corregir la plataforma que asignó el Strategist AI desde el panel (Banco de Ideas), sin tener que borrar y recrear. Campos opcionales: topic, platform, keyword, New angulo.
New angulo permite actualizar la justificación editorial junto con el título cuando se regenera una idea conservando su keyword. Antes se descartaba en silencio.
-d '{"platform":"Ambas"}'
platform válido: Facebook | Instagram | Ambas.
RRSS — Posts
auth:sanctumUn post es el contenido final listo para publicar. Cada post tiene copy adaptado para cada plataforma (Instagram, Facebook, LinkedIn), hooks alternativos para el inicio del texto, un CTA específico y opcionalmente una imagen o prompt para generarla con IA. El ciclo de vida es: el agente genera el borrador → el equipo lo revisa en el panel → lo aprueba → le asigna una fecha → queda programado. Cuando se publica, el sistema registra automáticamente quién lo aprobó y la fecha de publicación.
Filtros: ?status=borrador · ?platform=LinkedIn
Edita el contenido de un post desde el editor del panel. Campos opcionales: tema, platform, copy_instagram, copy_facebook, copy_linkedin, imagen_url, New video_url, hook, cta. El hook es un texto único (la primera línea que capta la atención); se persiste internamente en el array hooks.
-d '{"hook":"¿Sabías que el 80%...","cta":"Cotiza hoy","imagen_url":"https://.../post.png"}'
New 29-jul (B-14) — video_url, habilita Reels. Antes no existía la columna y el campo se descartaba en silencio, así que un post no podía llevar video adjunto. Se acepta en POST/PUT de posts y en POST /api/agent/rrss/posts, y se devuelve en GET /rrss/posts y GET /rrss/posts/to-publish — sin alias, se llama igual dentro y fuera. Debe ser una URL directa al archivo de video.
Elimina un post desde el panel. Solo se permiten posts en estado borrador — un post aprobado, programado o publicado devuelve 422. Útil para limpiar borradores duplicados generados en pruebas del Content AI.
Al aprobar → guarda approved_by_user_id. Al publicar → guarda fecha_publicada.
-d '{"status":"aprobado"}'
Programa post. Cambia status a programado. La fecha debe ser futura. New La hora se interpreta en la zona horaria de la empresa (companies.timezone); se guarda en UTC. Si la empresa no tiene zona, se asume UTC. Verifica que la empresa tenga su zona horaria establecida en Editar datos de la empresa → País / Zona horaria.
-d '{"fecha_programada":"2026-05-20T10:00:00"}'
New 29-jul (B-12) — al programar se avisa a la alarma de publicación. En el mismo momento en que se guarda la fecha, el backend hace una llamada única a la key rrss_schedule_alarm de agent_endpoints. Es dispara y olvida: el resultado no afecta la respuesta, y si el webhook falla o no está configurado el post queda programado igual (queda anotado en el log). Reemplaza al puente que revisaba cada 2 minutos.
// Lo que recibe el webhook:
{"post_id":40,"company_id":34,"fecha_programada":"2026-08-03T20:36:34Z"}
No es solo este endpoint. El aviso sale por los 4 caminos que dejan un post programado: este schedule, POST /api/rrss/posts creado ya con status:"programado", PUT /api/rrss/posts/{id} (el composer del calendario edita fecha y estado juntos) y PUT /api/rrss/posts/{id}/status.
Cuándo NO se avisa — para no mandar alarmas duplicadas ni inútiles: si solo se editó el contenido de un post ya programado (no cambió ni la fecha ni el estado); si la fecha quedó en el pasado; si el post no está en programado; o si la key rrss_schedule_alarm no está activa. Reprogramar a otra fecha sí vuelve a avisar, con la fecha nueva.
Sube una imagen desde el equipo del usuario para adjuntarla a un post (el editor del panel la usa cuando el operador arrastra o elige un archivo, como alternativa a pegar una URL). El backend optimiza la imagen: la reescala a un máximo de 1600px de ancho, la aplana sobre fondo blanco y la recomprime a JPEG (calidad 80). Devuelve una URL pública lista para guardar en imagen_url del post. multipart/form-data, campo file, máx. 10 MB, solo imágenes.
curl -s -X POST "https://api.growth54.com/api/images/upload" \
-H "Authorization: Bearer <token>" \
-F "file=@/ruta/imagen.png"
// Respuesta 201:
{"ok":true,"url":"https://.../storage/ai-images/<uuid>.jpg","bytes":184320}
RRSS — Métricas
auth:sanctumEl cierre del ciclo: después de publicar, el agente Analytics AI recoge los números reales de cada plataforma (alcance, impresiones, engagement, clics a perfil, leads generados) y los carga en la base de datos. La vista de métricas del panel muestra estos datos agrupados por plataforma y calcula promedios. También muestra el top 10 de posts por engagement, lo que permite al equipo identificar qué tipo de contenido funciona mejor para esa empresa.
Resumen de cuenta por plataforma. Filtros: ?platform=Instagram · ?from=2026-05-01 · ?to=2026-05-31
// Respuesta:
{"data":[{"platform":"Instagram","alcance_total":12430,"impresiones_total":38000,
"engagement_total":584,"engagement_rate_avg":4.7,"clics_total":384,"leads_total":17}]}
Cambió el 17-jul: (1) los números salen como número; antes eran texto ("5000") y el panel los concatenaba en vez de sumarlos (mostraba 050003200). (2) El agregado ahora excluye las filas con social_post_id: guardar métricas de un post ya no infla el total de la cuenta. Esas métricas siguen en top-posts.
Si el GET "no refleja lo que acabas de guardar": no es caché. Devuelve la suma del período, no el último valor — 16 sobre un histórico de 5000 da 5016. Además el guardado por agente usa el company_id del body y esta lectura usa la empresa activa de la sesión: si no coinciden, miras otra empresa.
Top 10 posts por engagement.
New 17-jul: métricas de cuenta agrupadas por número de semana relativo al fecha_inicio de la estrategia vigente. Sin estrategia con fecha responde has_strategy_dates:false y data:[]. Filtro opcional ?platform=Instagram.
// Respuesta:
{"data":[{"semana":1,"alcance_total":1000,"clics_total":50,"leads_total":5}],
"has_strategy_dates":true,"fecha_inicio":"2026-07-01","fecha_fin":"2026-10-01"}
RRSS — Reportes HTML
auth:sanctumNew · 29-julNew 29-jul (B-10). El Analytics AI genera un reporte de resultados en HTML; antes no había dónde guardarlo, así que el agente tenía que volver a generarlo cada vez que alguien quería verlo. Ahora se guarda en G54 y el panel lo lee del histórico. El agente escribe con POST /api/agent/rrss/reportes; estas 3 rutas son las del panel.
Histórico de la empresa activa, del más reciente al más viejo. No devuelve el html a propósito: un reporte mensual pesa cientos de KB y el listado cargaría megas por gusto.
// Respuesta:
{"data":[{"id":7,"titulo":"Reporte julio 2026","periodo_inicio":"2026-07-01",
"periodo_fin":"2026-07-31","modo":"completo","generated_by":"n8n","updated_at":"..."}]}
El reporte con su html completo, para mostrarlo o compartirlo. Un reporte de otra empresa devuelve 403.
Envía el reporte por correo a uno o varios destinatarios (máximo 20). Usa el SMTP de la propia empresa, el que ya está configurado en Ajustes → Correo electrónico, así el cliente lo recibe desde la dirección de su marca y no desde una de Growth54.
-d '{"destinatarios":["cliente@empresa.com","socio@empresa.com"],
"mensaje":"Hola, te comparto el reporte del mes."}'
// 200: {"ok":true,"message":"Reporte enviado."}
// 422: {"ok":false,"message":"Falta configurar el correo de la empresa..."}
El reporte viaja de dos formas a la vez: como cuerpo HTML (se ve al abrir el correo) y como archivo .html adjunto. El adjunto está porque Gmail y Outlook recortan CSS moderno, y así el reporte siempre se puede abrir intacto en el navegador. Se incluye también una versión en texto plano y una copia oculta al remitente, para que quede constancia en su bandeja.
Errores: 422 si la empresa no tiene correo configurado o activo, o si el servidor SMTP rechaza el envío (el mensaje incluye el motivo que devolvió el servidor). 403 si el reporte es de otra empresa. Un destinatario mal formado devuelve 422 de validación.
Borra un reporte del histórico. Un reporte de otra empresa devuelve 403.
RRSS — Pipeline de agentes
auth:sanctumNew · 29-julFuente de datos de las tarjetas del Dashboard de Agentes del panel (Strategist, Content, Distribution, Community, Sales, Analytics). Cada paso combina dos cosas distintas: si el agente existe y está configurado (fila activa con webhook_url en agent_endpoints) y qué produjo realmente para la empresa activa. Se documenta ahora porque no había forma de saber desde fuera de dónde salían esos números.
// Respuesta (un objeto por paso):
{"data":[{"key":"rrss_analytics","nombre":"Analytics AI","disponible":true,
"configurado":true,"estado":"listo","valor":"129.671","unidad":"de alcance acumulado",
"detalle":"2 lecturas de métricas.","accion":"analytics","ultima_actividad":"..."}],
"resumen":{"listos":4,"total_disponibles":6}}
estado: listo (ya produjo algo) · pendiente (configurado, sin usar) · sin_configurar (falta el webhook_url) · proximamente (el agente no existe todavía en agent_endpoints).
New Community AI y Sales AI dejan de estar clavados en "Próximamente". Antes su estado estaba escrito a mano en el código; ahora se deriva de agent_endpoints igual que el resto: si existen las keys rrss_community_ai / rrss_sales_ai activas y con webhook, las tarjetas pasan a estado real. Su actividad se mide con los contactos (wa_contacts) y deals (crm_deals) cuyo origen es facebook o instagram.
New Analytics AI mostraba 0 de alcance acumulado teniendo métricas reales. Contaba social_post_id distintos, y las métricas de cuenta se guardan con ese campo en NULL — COUNT(DISTINCT NULL) da 0 y escondía el total. Ahora cuenta filas y agrega el alcance con el mismo criterio que GET /api/rrss/metricas (solo filas de cuenta, para no duplicar), con las filas por post como respaldo si no hay ninguna de cuenta. Los dos números ahora coinciden.
RRSS — Triggers de agentes
auth:sanctumCuando el usuario hace clic en "Ejecutar agente" en el panel, el frontend llama uno de estos endpoints. El backend busca la key del agente en la tabla agent_endpoints, obtiene la URL del webhook de n8n y lo dispara pasándole el company_id. Si el webhook no está configurado, responde con 422 y un mensaje indicando que hay que configurarlo en Panel › Configuración › Agentes. El agente trabaja de forma asíncrona: el webhook responde inmediatamente con 200 y n8n procesa en segundo plano.
Key: rrss_strategist
New · 6-ago (I-1) Acepta el brief del cliente, ambos campos opcionales: horizonte_meses (solo 1, 3, 6 u 8; otro valor da 422) y direccion_deseada (texto libre, máx. 2000). El panel los pide en un modal antes de disparar el agente. Cuando llega horizonte_meses, el backend calcula además el bloque periodo con las fechas ya resueltas, para que el agente no tenga que hacer aritmética de calendario. Sin brief el payload sale idéntico a como salía antes.
-d '{"horizonte_meses": 6, "direccion_deseada": "Posicionar la marca como referente"}'
# lo que recibe n8n:
{
"company_id": 19,
"trigger": {"source": "ui", "user_id": 1},
"horizonte_meses": 6,
"periodo": {"fecha_inicio": "2026-08-07", "fecha_fin": "2027-02-07"},
"direccion_deseada": "Posicionar la marca como referente"
}
Key: rrss_content_ai. mode: ideas | post. idea_id se reenvía a n8n.
New 17-jul (I-5): el botón "Regenerar" de una idea llama a este endpoint con mode:"ideas" y el idea_id de esa idea. Acción para Sol: el flujo ideas-ai-g54 debe, cuando llega idea_id, regenerar esa idea conservando su keyword (en vez de generar un lote nuevo).
New 29-jul — por qué ideas-ai-g54 no registra ejecuciones: el botón sí dispara. El panel nunca llama a un webhook de n8n directamente: llama a este endpoint y el backend reenvía a la URL guardada en agent_endpoints para la key rrss_content_ai. Si esa fila apunta a otro webhook, el flujo ideas-ai-g54 no ve nada. Además el contrato es el de arriba (mode/ideas), no modo/regenerar: los nombres deben coincidir. Para conectarlo hay que alinear ambas cosas — la URL de la fila y los nombres de campos.
-d '{"mode":"ideas","idea_id":null}' // genera lote nuevo
-d '{"mode":"ideas","idea_id":5}' // regenera la idea 5 con su keyword
-d '{"mode":"post","idea_id":5}'
Key: rrss_analytics
n8n — Lectura general
Bearer N8N_TOKENEstos endpoints existen para que los workflows de n8n puedan leer el contexto de la empresa antes de generar contenido. Un agente que va a escribir artículos necesita saber la industria de la empresa, sus keywords, sus productos. Un agente RRSS necesita saber en qué plataformas está activa la empresa y cuál es su tono de comunicación. Todos son de solo lectura — los agentes leen desde aquí y escriben los resultados a través de los endpoints /api/agent/*.
Lista todas las empresas.
Perfil de empresa (sin datos sensibles).
Filtros: ?estado=activa · ?origen=manual
Solo keywords sin artículo.
Detalle con HTML del reporte.
n8n — Lectura RRSS
Bearer N8N_TOKENBase: /api/n8n/companies/{id}/rrss/
Canales activos con handle, objetivo, frecuencia y rol en el embudo.
null si no existe.
Solo ideas con status aprobado.
Posts con status aprobado o programado.
image_url New — La columna interna se llama imagen_url, pero GET /rrss/posts y GET /rrss/posts/to-publish ahora devuelven también el campo image_url (mismo valor) para los agentes. La imagen es obligatoria para publicar en Instagram; si viene vacía, el post solo puede publicarse como texto en Facebook.video_url New · 29-jul — Ambas lecturas devuelven además video_url cuando el post lleva video adjunto (Reels). No tiene alias: se llama igual dentro y fuera. Si viene vacío, el post se publica como imagen o texto según corresponda.Traduce un page_id de Meta a la empresa dueña. Meta manda el page_id en el webhook pero no el company_id; esta es la vía para resolverlo (Community AI multiempresa). Devuelve 404 si el page_id no está registrado.
{"company_id": 19, "fb_access_token": "EAA...", "ig_user_id": "17841446392201293"}
ig_user_id ya no vuelve vacío New · 6-ago — Un mismo page_id vive en dos filas de company_social_tokens (el unique de la tabla es empresa+plataforma): la de Facebook, sin instagram_account_id, y la de Instagram, con él. La resolución tomaba una sola fila sin ordenar y casi siempre caía en la de Facebook, devolviendo ig_user_id: null aunque el dato estuviera guardado. Ahora se busca en todas las filas activas de ese page_id y, si la cuenta de Instagram se cargó a mano sin page_id, se resuelve dentro de la misma empresa. fb_access_token sigue saliendo de la fila de Facebook.Agent — Escritura general
Bearer AGENT_TOKENUna vez que el agente termina su trabajo (generar keywords, escribir artículos, crear auditorías), usa estos endpoints para guardar los resultados en Growth54. Se identifican por el AGENT_API_TOKEN, distinto al token de usuario, lo que significa que el agente puede escribir datos sin tener una sesión de usuario activa. Todos los registros creados por esta vía quedan marcados con generated_by = 'n8n' para distinguirlos de los creados manualmente.
Inserta o actualiza keywords en bulk. skip_existing: true no toca las existentes.
-d '{"empresa_id":1,"skip_existing":true,"keywords":[
{"keyword":"abogado laboral","dificultad_nivel":"media","intencion":"comercial","fuente":"Google"}
]}'
-d '{"empresa_id":1,"articulos":[{"keyword_id":10,"titulo":"Guía de contrato","contenido_html":"<h1>...","estado":"borrador"}]}'
Guarda o actualiza la auditoría inicial (upsert por empresa).
Agent — Escritura RRSS
Bearer AGENT_TOKENTodos los registros creados quedan con generated_by = 'n8n' automáticamente.
New 17-jul: acepta fecha_inicio, fecha_fin y direccion_deseada (igual que el PUT del panel). Sigue haciendo updateOrCreate por empresa.
New 29-jul (B-17): acepta también resumen_estrategico, keyword_strategy, mapa_preguntas_aeo, faqs, clusters y plan_publicacion. Ver el ejemplo completo en RRSS — Estrategia.
-d '{"company_id":3,"fecha_inicio":"2026-07-01","fecha_fin":"2026-10-01",
"direccion_deseada":"...","objetivo":"...","audiencia":"...","tono":"...",
"plataformas":["Instagram","LinkedIn"],"pilares":["Educación"],"kpis":["Alcance"]}'
Bulk. Respuesta: {"ok":true,"created":N}
New Ahora acepta y guarda angulo (justificación de la idea, texto — se muestra al cliente bajo el tema) y es_tendencia (booleano — pinta el badge "Tendencia" en el panel). Ambos opcionales; si no se mandan, la idea queda sin justificación y sin marca.
-d '{"company_id":3,"ideas":[{"topic":"5 errores PYME","platform":"Instagram","keyword":"marketing","angulo":"Aborda el dolor #1 de las PYMEs este trimestre","es_tendencia":true}]}'
Bulk. Llegan con status = borrador.
-d '{"company_id":3,"posts":[{
"tema":"5 errores PYME","platform":"Instagram","social_idea_id":5,
"copy_instagram":"¿Tu negocio está en IG pero no consigue resultados?\n...",
"copy_linkedin":"En 2026, producir sin estrategia es el error más común...",
"hook":"¿Sabías que el 80% de las PYMEs comete este error?",
"cta":"Comenta o DM para auditoría gratuita.",
"imagen_prompt":"Minimalist flat design 5 warning signs PYME"
}]}'
Campo hook (singular) New — El Content AI puede enviar hook como un solo texto (la primera línea del post). Se guarda y se edita igual desde el panel. Sigue aceptándose hooks (array) por compatibilidad, pero se recomienda hook.
New 29-jul (B-14): acepta posts.*.video_url para posts con video (Reels). Ver RRSS — Posts.
New 29-jul (B-10). Guarda el reporte HTML de un período para que quede disponible en el panel. Es upsert por company_id + periodo_inicio + periodo_fin + modo: regenerar el reporte de un mes reemplaza el anterior en vez de acumular copias. Devuelve 201 si lo creó y 200 si lo actualizó.
-d '{"company_id":3,"titulo":"Reporte julio 2026",
"periodo_inicio":"2026-07-01","periodo_fin":"2026-07-31",
"modo":"completo","html":"<html><body>...</body></html>"}'
// Respuesta 201 (no devuelve el html de vuelta):
{"ok":true,"data":{"id":7,"titulo":"Reporte julio 2026",
"periodo_inicio":"2026-07-01","periodo_fin":"2026-07-31","modo":"completo"}}
Obligatorios: company_id, titulo, periodo_inicio, periodo_fin, html. modo es opcional y sirve para guardar varias versiones del mismo período (por ejemplo resumen y completo) sin que una sobrescriba a la otra. Un periodo_fin anterior al inicio devuelve 422. El HTML se guarda en longText: probado con 360 KB sin truncar. Las rutas de lectura están en RRSS — Reportes HTML.
Borra un post (limpieza de duplicados de pruebas). Solo borradores: un post aprobado/programado/publicado devuelve 422 con {"ok":false,"error":"..."}.
Lo llama el Distribution AI después de intentar publicar. Cambió el 17-jul: antes el external_post_id se validaba pero nunca se guardaba, y un fallo dejaba el post atascado en programado sin aviso.
// Éxito:
-d '{"external_post_id":"17912345678901234",
"link_url":"https://www.instagram.com/p/ABC123/"}'
// → status=publicado · guarda post_id_platform + link_url · limpia error_message
// → {"ok":true,"data":{...post...}}
// Fallo:
-d '{"error_message":"Instagram requiere imagen: el post no tiene imagen_url"}'
// → el post VUELVE a `aprobado` y guarda el motivo (el cliente lo ve en el panel)
// → {"ok":false,"message":"..."}
link_url es un parámetro nuevo, opcional. Un post ya publicado no se revierte. Enviar external_post_id:"" no borra un ID ya guardado; llamar dos veces es seguro.
⚠️ Para reportar fallos usa esta ruta. PUT /api/rrss/posts/{id}/status no acepta el AGENT_TOKEN (da 401) y seguirá así: resuelve la empresa desde el usuario de la sesión, y un agente no tiene usuario. Si reportas el fallo por ahí, el post se queda atascado y el cliente no ve nada.
Acumula, no sobrescribe: cada llamada inserta una fila nueva con su período. Manda social_post_id para métricas de un post; omítelo para las de cuenta. New Desde el 17-jul las métricas por post ya no inflan el agregado de cuenta.
-d '{"company_id":3,"metricas":[{
"platform":"Instagram","period_start":"2026-05-01","period_end":"2026-05-31",
"social_post_id":12,"alcance":3420,"impresiones":10500,
"engagement_count":212,"engagement_rate":6.2,"clics":89,"leads":4
}]}'
CRM — Deals
auth:sanctumNewEl pipeline comercial. Un deal es la ficha de un negocio: puede nacer de un lead de WhatsApp, del formulario web o a mano. Documentado el 17-jul a raíz de dos reportes del equipo de agentes.
Corregido el 17-jul: telefono y origen ya se pueden editar. Antes la ruta respondía 200 pero ignoraba esos dos campos en silencio (no estaban en la validación), así que un dato mal guardado ahí era incorregible.
-d '{"telefono":"+593999888777","origen":"agente"}'
// → 200 y ahora sí persiste
// Campos editables:
// nombre · etapa · valor_estimado · responsable · lead_score
// temperatura · notas · posicion · telefono (New) · origen (New)
Solo afecta a deals de la empresa activa; el de otra empresa devuelve 404.
Valores válidos de origen: whatsapp · web · manual · agente · importacion · New facebook · instagram. Es un enum de base de datos. 17-jul (B-5): se agregaron facebook e instagram vía migración; antes no existían. En contactos de WhatsApp el enum es distinto: rrss · web · qr · ai · direct · New facebook · instagram.
New 17-jul: un origen fuera del enum, o un telefono de más de 30 caracteres, ahora devuelven 422 con el campo señalado. Antes reventaban con 500 y el SQL crudo (1265 Data truncated / 1406 Data too long).
Agentes n8n — Config inicial
Todos los workflows empiezan con un nodo Set llamado Config.
BASE_URL = https://api.growth54.com ← producción
https://xxx.ngrok-free.app ← local con ngrok
AGENT_TOKEN = (AGENT_API_TOKEN del .env)
N8N_TOKEN = (N8N_API_TOKEN del .env)
COMPANY_ID = {{ $json.company_id }} ← viene del trigger del panel
Authorization: Bearer <N8N_TOKEN>Escritura →
Authorization: Bearer <AGENT_TOKEN>
| Agente | Key |
|---|---|
| Strategist AI | rrss_strategist |
| Content AI (ideas + post) | rrss_content_ai |
| Analytics AI | rrss_analytics |
Strategist AI
rrss_strategistLee perfil y canales, genera estrategia RRSS completa, la guarda. El equipo puede editarla desde el panel.
company_idGET /api/n8n/companies/{id} — nombre, industria, paísGET /api/n8n/companies/{id}/rrss/canalesGET /api/n8n/companies/{id}/rrss/estrategia — puede ser nullPOST /api/agent/rrss/estrategiaEres un estratega RRSS experto en PYMEs latinoamericanas.
Empresa: {nombre} — {industria} — {país}
Canales activos: {plataforma, handle, objetivo, frecuencia}
Estrategia actual: {existente o "ninguna"}
Responde SOLO en JSON:
{"objetivo":"...","audiencia":"...","tono":"...","frecuencia":"...",
"cta_principal":"...","embudo":"...","plataformas":[],"pilares":[],"kpis":[]}
Content AI — dos botones, un agente
rrss_content_aiNew La pantalla RRSS › Ideas del panel tiene dos botones separados que disparan el mismo webhook (rrss_content_ai). Lo único que cambia es el campo mode; el agente de n8n debe ramificar según mode (nodo Switch al inicio):
mode: "ideas" → genera ideas nuevas (sin correr el Strategist completo) → POST /api/agent/rrss/ideasmode: "post" (opcional idea_id) → genera el borrador → POST /api/agent/rrss/postsPayload exacto que recibe el webhook (lo arma RrssAgentTriggerController):
{
"company_id": 24,
"trigger": { "source": "ui", "user_id": 1 },
"mode": "ideas", // "ideas" (Generar Ideas) o "post" (Generar Post)
"idea_id": null // sólo en mode "post": la idea aprobada a desarrollar
}
Importante: si el agente ignora mode y siempre genera posts, el botón "Generar Ideas" saldrá mal (era el bug anterior). El branch por mode es obligatorio.
Content AI — modo Ideas
mode: ideasGenera 10 ideas basadas en la estrategia y canales. Llegan con status = idea para revisión del equipo.
company_id, mode = "ideas"GET perfil + canales + estrategiaPOST /api/agent/rrss/ideas — bulk, respuesta: {"ok":true,"created":10}Genera 10 ideas de contenido para {nombre} ({industria}, {país}).
Objetivo: {objetivo} | Pilares: {pilares} | Canales: {plataformas}
Sé específico. Fechas: próximos 30 días.
Responde SOLO en JSON:
{"ideas":[{"topic":"...","platform":"Instagram|Facebook|LinkedIn",
"keyword":"...","fecha_propuesta":"YYYY-MM-DD"}]}
Content AI — modo Post
mode: postToma una idea aprobada y genera el post completo: copy por plataforma, hooks y CTA. Llega como borrador.
company_id, mode = "post", idea_idGET perfil + estrategia + idea específicaPOST /api/agent/rrss/posts — borrador con social_idea_id enlazadoEres copywriter experto en RRSS para PYMEs de LATAM.
Empresa: {nombre} | Tono: {tono} | CTA: {cta_principal}
Tema: {topic} | Plataforma: {platform} | Keyword: {keyword}
Responde SOLO en JSON:
{"copy_instagram":"texto con emojis y hashtags","copy_facebook":"texto conversacional",
"copy_linkedin":"tono profesional","hooks":["hook1","hook2"],
"cta":"llamada a la acción","imagen_prompt":"descripción en inglés para DALL-E"}
Analytics AI
rrss_analyticsLee métricas de las plataformas y las carga al backend.
company_idGET /api/n8n/companies/{id}/rrss/canales — handles y plataformasGET /api/n8n/companies/{id}/rrss/posts — para linkear métricas por postPOST /api/agent/rrss/metricasOrden de construcción recomendado
| # | Agente | Por qué primero |
|---|---|---|
| 1 | Content AI — Ideas | Valor inmediato. El cliente ve ideas en segundos. |
| 2 | Content AI — Post | Amplía el anterior. Misma key, mismo workflow. |
| 3 | Strategist AI | Requiere canales bien configurados primero. |
| 4 | Analytics AI (Sheets) | Mínimo viable. Sin OAuth por empresa. |
| 5 | Analytics AI (APIs) | Automatización total. Iteración avanzada. |
- No enviar
company_id/empresa_iden el body → 422 statuscon valor fuera del enum → 422fecha_programadaen el pasado → rechazada- Omitir
Authorizationheader → 401
Telegram — Webhook de entrada
New · 22-julTelegram entrega los mensajes directamente a Growth54, sin pasar por n8n. A diferencia de Meta, no exige revisión de app ni verificar la empresa: basta un bot creado con @BotFather.
Lo llama Telegram, no una persona. Se protege con dos cosas a la vez: el {secret} de la URL,
que identifica a la empresa, y la cabecera X-Telegram-Bot-Api-Secret-Token, comparada en
tiempo constante. Si algo no cuadra responde 404 sin dar pistas.
Siempre responde 200, incluso si algo falla al guardar: si devolviera error, Telegram
reintentaría y acabaría duplicando el mensaje. Los updates que no son texto (fotos, stickers) se
ignoran con {"ok":true,"ignorado":true}.
{
"update_id": 1,
"message": {
"chat": { "id": 555001, "type": "private" },
"from": { "id": 555001, "first_name": "Walter", "username": "waltergz" },
"text": "Hola, quiero información"
}
}
Qué guarda: contacto en wa_contacts con canal = telegram y
external_id = chat_id, conversación en wa_conversations y el mensaje con
rol = user. Las tablas wa_* son ya la bandeja de todos los
canales, no solo de WhatsApp: por eso el CRM enlaza los leads de Telegram sin cambios.
Telegram — Relevo al agente
New · 22-julGrowth54 recibe y guarda siempre; después, si hay un agente configurado, le reenvía el mensaje para que responda. El reparto es: nosotros el transporte y el archivo, el agente el cerebro.
Se registra en Configuración → Agentes con la clave telegram_agent. Growth54
le hace POST a su webhook con este cuerpo:
{
"empresa_id": 23,
"canal": "telegram",
"chat_id": "555001",
"contact_id": 178,
"conversation_id": 116,
"nombre": "Walter Garcia",
"texto": "Hola, quiero información"
}
Para responder, el agente puede usar la API de Telegram con el token del bot, o el endpoint del panel de más abajo. Lo que no debe hacer es registrar su propio webhook en el bot: desconectaría el nuestro.
Telegram — Panel
New · 22-julLectura y gestión desde el panel, siempre acotado a la empresa activa del usuario.
| Método | Ruta | Qué hace |
|---|---|---|
| GET | /api/telegram/stats | Conversaciones, contactos, leads, calientes y negocios en el CRM |
| GET | /api/telegram/conversations | Lista paginada, con el último mensaje de cada una |
| GET | /api/telegram/conversations/{id} | Hilo completo |
| POST | /api/telegram/reply | Responder. Envía directo por la API de Telegram, sin n8n |
| GET | /api/telegram/config | Estado del bot. El token nunca se devuelve: solo si está puesto y sus últimos dígitos |
| PUT | /api/telegram/config | Guardar token y conectar, o {"is_active": false} para desconectar |
| GET | /api/telegram/webhook-info | Diagnóstico: qué webhook tiene Telegram registrado ahora mismo |
wa_reply. Aquí el
envío es directo, y el estado del mensaje (enviado / fallido) refleja lo que
Telegram respondió de verdad — el acuse no miente.
Al guardar el token, el backend lo verifica con getMe contra Telegram antes
de activar nada y registra el webhook solo. Si el token no sirve responde 422 con el motivo
que dio Telegram. El estado de conexión se deriva de esa comprobación real, no se declara.
Correo electrónico — Configuración
New · 22-julDatos de acceso de la cuenta de correo del cliente. Esta fase solo guarda y verifica: la entrada de correos como conversaciones llega después.
| Método | Ruta | Qué hace |
|---|---|---|
| GET | /api/email/config | Cuenta guardada y valores por defecto de Gmail, Outlook y otros |
| PUT | /api/email/config | Guardar. Si secret llega vacío se conserva la contraseña anterior |
| POST | /api/email/config/test | Abre una conexión IMAP real y hace LOGIN para comprobar las credenciales |
WhatsApp Engine — Inbound (agente n8n)
AGENT_API_TOKENEndpoint que el agente n8n llama cada vez que llega un mensaje de WhatsApp. Crea o actualiza el contacto, registra los mensajes y programa follow-ups automáticos. No requiere que exista un número de WhatsApp real — el agente puede llamarlo con datos de cualquier canal mientras se integra Meta API.
curl -s -X POST "https://api.growth54.com/api/agent/whatsapp/inbound" \
-H "Authorization: Bearer $AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"empresa_id": 1,
"phone": "+50588001234",
"name": "Carlos Medina",
"estado": "hot",
"origen": "rrss",
"datos_calificacion": {
"carga": "maquina industrial",
"destino": "Colombia",
"peso_lbs": 300
},
"messages": [
{ "rol": "bot", "contenido": "Hola, ¿en qué te ayudo?", "sent_at": "2026-05-13 10:32:00" },
{ "rol": "user", "contenido": "Quiero cotizar una exportación", "sent_at": "2026-05-13 10:33:00" },
{ "rol": "sys", "contenido": "Lead clasificado como CALIENTE" }
],
"followup": {
"tipo": "24h",
"scheduled_at": "2026-05-14 10:33:00",
"mensaje": "Hola Carlos, ¿sigues interesado en la cotización?"
}
}'
Comportamiento: si el contacto (empresa_id + phone) ya existe, actualiza nombre/estado/origen/calificación. Si la conversación activa ya existe, agrega los mensajes a ella. Si se envía followup, crea un registro en wa_followups.
Campos obligatorios: empresa_id, phone, messages (mínimo 1).
Valores válidos: estado: hot | warm | cold — origen: rrss | web | qr | ai | direct | New facebook | instagram — messages.*.rol: bot | user | sys — followup.tipo: 24h | 72h | 30d | custom.
New 29-jul (B-15): facebook e instagram devolvían 422 aunque el enum de wa_contacts ya los aceptaba desde el 17-jul: faltaba agregarlos a la validación de esta ruta. Ya corregido — se pueden volver a mandar.
WhatsApp Engine — Leads (lectura n8n)
Bearer N8N_TOKENNewEndpoint que el agente consulta al llegar cada mensaje para recuperar la memoria de la conversación. Sin esto, el bot trata cada mensaje como un cliente nuevo y repite preguntas ya hechas. Devuelve el contacto con su historial completo (todos los turnos anteriores) y los datos ya recolectados. Es solo lectura: lo que se escribe sigue yendo por POST /api/agent/whatsapp/inbound.
El phone va url-encoded (ej. %2B17867880417 para +17867880417).
curl -s "https://api.growth54.com/api/n8n/companies/1/wa/leads?phone=%2B17867880417" \
-H "Authorization: Bearer $N8N_TOKEN"
# Respuesta (lead existente):
{
"data": [{
"id": 7,
"contact_name": "Carlos Medina",
"phone": "+17867880417",
"estado": "hot",
"historial": "Agente: Hola, ¿en qué te ayudo?\nCliente: Quiero cotizar",
"datos": { "producto": "cajones cerrados", "medidas": "40x30x20" },
"created_at": "2026-06-20T14:08:00Z",
"updated_at": "2026-06-26T14:08:00Z"
}]
}
# Sin lead para ese teléfono:
{ "data": [] }
Flujo: llega mensaje → GET wa/leads. Si data[0] existe → carga historial y datos (el bot recuerda). Si data[] vacío → cliente nuevo, saluda desde cero. Al responder → POST /api/agent/whatsapp/inbound actualiza historial y datos.
Notas: estado es del contacto (hot | warm | cold); la calificación fina vive en datos (de wa_contacts.datos_calificacion). El historial rotula los turnos como Cliente / Agente / Sistema (mapeo de user / bot / sys).
WhatsApp Engine — Leads (borrado / reset de pruebas)
Bearer AGENT_TOKENNewEndpoints de escritura para que el agente (o quien prueba) limpie leads de WhatsApp sin entrar a la base de datos. Usan el token de agente (AGENT_API_TOKEN), no el N8N_TOKEN de lectura. Al borrar un lead se eliminan en cascada sus conversaciones, mensajes y follow-ups, más el deal del CRM ligado. La empresa va en la URL, así que quedan aislados por empresa.
{"confirm":"DELETE"} en el body como seguro anti-accidentes: evita vaciar la empresa equivocada por un {company} mal escrito.Borra de golpe todos los leads de WhatsApp de la empresa. Ideal para resetear entre pruebas.
curl -s -X DELETE "https://api.growth54.com/api/agent/companies/1/whatsapp/leads" \
-H "Authorization: Bearer $AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"confirm":"DELETE"}'
# Respuesta:
{
"ok": true,
"company_id": 1,
"deleted": { "contacts": 12, "deals": 4 },
"message": "Todos los leads de WhatsApp de la empresa fueron eliminados (contactos, conversaciones, mensajes, followups y deals)."
}
Borra un lead puntual (por id de contacto). No requiere confirm. Devuelve 404 si el contacto no existe o no pertenece a esa empresa.
curl -s -X DELETE "https://api.growth54.com/api/agent/companies/1/whatsapp/leads/7" \
-H "Authorization: Bearer $AGENT_TOKEN"
# Respuesta:
{
"ok": true,
"company_id": 1,
"contact_id": 7,
"deleted_deals": 1,
"message": "Lead de WhatsApp eliminado (contacto, conversaciones, mensajes, followups y deals asociados)."
}
WhatsApp Engine — Panel (Sanctum)
auth:sanctumEndpoints que consume el frontend para mostrar conversaciones, leads y follow-ups. Todos operan sobre la empresa activa del usuario autenticado (current_company_id).
Totales del panel: conversaciones, leads (hot+warm), hot, cotizaciones y ventas_cerradas. Los dos últimos provienen del módulo CRM (deals en etapa proposal+ y closed) — solo lectura, no afectan a los agentes.
Lista paginada de conversaciones con datos del contacto y último mensaje. Ordenadas por updated_at descendente.
Conversación completa: datos del contacto + todos los mensajes ordenados por sent_at.
Follow-ups activos (estado: pendiente | programado | activo) ordenados por scheduled_at. Incluye nombre y teléfono del contacto.
curl -s -X POST "https://api.growth54.com/api/whatsapp/reply" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"conversation_id": 12,
"message": "Hola, te confirmo la cotización."
}'
Respuesta manual del operador desde el panel. Guarda el mensaje saliente (rol: bot) en la conversación de la empresa activa y lo reenvía a n8n como transporte hacia WhatsApp (Graph API). La conversación debe pertenecer a la empresa activa.
Devuelve New: siempre 201 cuando el mensaje se guarda, con { ok, sent, data, message }. El mensaje guardado incluye ahora estado (enviado | fallido | pendiente) y transporte_detalle (código HTTP y respuesta de n8n, o el error de red). ok y sent valen false si el transporte no aceptó el envío. Antes, si el webhook no estaba configurado se devolvía 422; ahora el mensaje se guarda y se marca fallido.
⚠️ Alcance de sent: significa que n8n aceptó el POST, no que Meta haya entregado el mensaje al cliente. Los acuses reales de Meta (sent → delivered → read) aún no se reciben.
curl -s -X DELETE "https://api.growth54.com/api/whatsapp/leads/45" \
-H "Authorization: Bearer $TOKEN"
Reset total de un lead de WhatsApp (herramienta de debugging para limpiar pruebas). El {id} es el contact.id que devuelve GET /api/whatsapp/conversations. Borra el contacto y todo lo que cuelga de él: conversaciones, mensajes, follow-ups y su(s) ficha(s) del CRM (con sus actividades y eventos). No hay UI: es solo API a propósito, por ser destructivo.
Aislado por empresa activa: solo borra leads de la empresa del token; un id de otra empresa devuelve 404. Respuesta 200: { ok, contact_id, deleted_deals, message }.
POST https://n8n.mdarthurdigital.com/webhook/wa-reply-g54
Content-Type: application/json
Authorization: Bearer $N8N_WA_REPLY_WEBHOOK_TOKEN # solo si el env está definido
{
"phone": "+5215512345678",
"message": "Hola, te confirmo la cotización.",
"company_id": "20",
"phone_number_id": "1083260611538246"
}
Contrato de salida. Growth54 envía exactamente esos cuatro campos. El destino se configura en agent_endpoints (key wa_reply) o en el env N8N_WA_REPLY_WEBHOOK_URL. Timeout: 12 s (6 s de conexión). Cualquier respuesta 2xx se interpreta como aceptada.
New phone_number_id: el número de Meta de la empresa activa, tomado de wa_configs. Sirve para resolver las credenciales de forma dinámica, sin cablearlas por empresa: pasarlo a GET /api/agent/wa/empresa-by-phone/{phone_number_id}, que devuelve company_id, wa_access_token y el resto de la config.
Si la empresa no tiene WhatsApp configurado, Growth54 no llama al webhook y marca el mensaje como fallido.
⚠️ n8n (lado Solange) — cuatro condiciones:
- Solo transporte. Enviar a WhatsApp por Graph API, pero NO volver a guardar el mensaje en G54: el panel ya lo persiste y se duplicaría. (La API solo acepta
rolbot,userosys;agent/agentedevuelve 422.) - Nombres de campos. El flujo debe leer
phone,message,company_idyphone_number_idtal cual (notelefono,texto,mensajenito). - Credenciales dinámicas. Resolver el token con
empresa-by-phoneusando elphone_number_idrecibido. No cablear tokens por empresa: no escala multiempresa. - Autenticación. Si el webhook exige token, avisar para definir
N8N_WA_REPLY_WEBHOOK_TOKEN; hoy no se envía cabeceraAuthorization.
Responder 2xx solo si el envío a Meta salió bien. Si n8n responde 200 antes de llamar a Graph API, el panel marcará enviado un mensaje que nunca llegó.
Casos de uso
GET /api/n8n/companies→ obtenerempresa_idPOST /api/agent/keywords/bulk→ insertar keywords
GET /api/n8n/companies/{id}/keywords/pending-articulos- El agente genera HTML para cada keyword
POST /api/agent/articulos
- Panel llama
POST /api/rrss/run-content-aiconmode: ideas - n8n lee estrategia y canales → IA genera ideas →
POST /api/agent/rrss/ideas - El equipo aprueba desde el panel →
PUT /api/rrss/ideas/{id}/status
Planes de Suscripción
Documentación completa de los planes disponibles en Growth54: funcionalidades incluidas, límites por plan y comparativa. Relevante para el equipo comercial y para entender qué módulos están disponibles para cada cliente.
💳 Abrir documento de planes →Checklist rápido
- Insertar datos desde n8n:
/api/n8n/companies→ id →/api/agent/* - Acceso de usuario: login → switch-company → usar módulos
- Activar protección: definir
N8N_API_TOKENyAGENT_API_TOKENen.env - Siempre enviar:
Accept: application/jsonyContent-Type: application/json
POST /api/dev/purge-current-company elimina todos los datos de la empresa activa. Nunca en producción.