Documentación API — Growth54

Laravel 12 REST + JSON Sanctum Base: 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.

Modo provisional (entorno de desarrollo) — Durante el desarrollo, si 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.

TipoPrefijoQuié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
Concepto clave — empresa activa: un usuario puede ser miembro de varias empresas a la vez (por ejemplo, un consultor que gestiona varios clientes). Todos los módulos del panel (keywords, artículos, RRSS, etc.) trabajan sobre una empresa a la vez, la que esté activa en ese momento. Si el usuario no tiene empresa activa asignada, la API responde 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

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

POST/api/public/register-client

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
GET/api/health

Verifica que la app, base de datos y caché respondan.

curl -s "https://api.growth54.com/api/health" | jq
👤

Login de usuario

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

POST/api/auth/login
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
GET/api/auth/metoken

Datos del usuario autenticado.

POST/api/auth/switch-companytoken

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
La empresa activa ahora es por sesión New · 6-ago (B-19) — Antes vivía en la fila del usuario, así que dos sesiones de la misma cuenta se pisaban: si una hacía 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.
POST/api/auth/logouttoken

Invalida el token actual.

🛡️

Admin master

POST/api/admin/login
curl -s -X POST "https://api.growth54.com/api/admin/login" \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@demo.com","password":"secret"}' | jq
GET/api/admin/me
GET/api/admin/usersCRUD completo

GET lista · POST crear · GET/{id} · PUT actualizar · DELETE

GET/api/admin/companiesCRUD completo

Al crear con owner_user_id asigna rol owner automáticamente.

GET/api/admin/plansCRUD completo
GET/api/admin/roles
🚀

Onboarding

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

POST/api/onboarding/companies

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
POST/api/onboarding/join

Unirse con código de invitación. Rol: collaborator.

-d '{"code":"ABC123"}'
POST/api/companies/{company}/invites

Genera código de invitación. Solo owner o admin.

-d '{"max_uses": 10, "expires_in_days": 30}'
🔑

Keywords

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

El flujo típico es: el agente de keyword research encuentra oportunidades → las guarda vía /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.
GET/api/keywords

Lista keywords de la empresa activa.

GET/api/keywords/stats

Totales por dificultad, intención, etc.

POST/api/keywords

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"}'
PATCH/api/keywords/{id}
POST/api/keywords/run-n8n-keyword-research

Dispara el agente de keyword research para la empresa activa.

POST/api/keywords/{id}/run-n8n-articulo

Dispara generación de artículo para una keyword específica.

📝

Artículos

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

GET/api/articulos

Lista (sin HTML). estado: borrador/revisado/publicado

GET/api/articulos/{id}

Detalle completo con HTML.

PATCH/api/articulos/{id}
POST/api/articulos/{id}/publish-wordpress

Marca como publicado y asigna fecha.

🔍

Auditorías

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

GET/api/auditorias

Lista (sin HTML).

GET/api/auditorias/{id}

Detalle con HTML del reporte.

📊

Auditoría inicial

Cada 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).

GET/api/auditoria-inicial
POST/api/auditoria-inicial/run-n8n

Dispara el workflow n8n. El agente guarda el resultado vía /api/agent/auditoria-inicial.

🌐

Páginas / URLs

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

GET/api/paginas
POST/api/paginas

Registra nueva URL del sitio.

POST/api/paginas/discover

Descubrimiento automático de páginas.

POST/api/paginas/{id}/auditoria-seo/run-n8n

Dispara auditoría SEO para una URL específica.

🛍️

Productos y servicios

Catá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.

GET/api/productos-servicios
POST/api/productos-servicios
PATCH/api/productos-servicios/{id}
DELETE/api/productos-servicios/{id}
🎯

Competidores

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

GET/api/competitors
POST/api/competitors
PATCH/api/competitors/{id}
DELETE/api/competitors/{id}
POST/api/agent/competitors/bulkagent token

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

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

GET/api/cms-integrations
POST/api/cms-integrations
PATCH/api/cms-integrations/{id}
DELETE/api/cms-integrations/{id}
GET/api/agent/companies/{id}/cms/{provider}agent token siempre

Credenciales CMS para que el agente pueda publicar. Siempre requiere token aunque el servidor esté en modo provisional.

⚙️

Agent Endpoints

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

Key nueva 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".
GET/api/agent-endpoints
POST/api/agent-endpoints
PATCH/api/agent-endpoints/{id}
POST/api/agent-endpoints/{id}/activate

Activa o desactiva el agente.

POST/api/agent-endpoints/{id}/test

Prueba la conectividad con el webhook.

DELETE/api/agent-endpoints/{id}
Distribution AI registrado New — Se agregó la key 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

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

GET/api/social-channels
PUT/api/social-channels Nuevo · image_config

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

Una empresa tiene exactamente una estrategia RRSS. El PUT es upsert.

GET/api/rrss/estrategia

data: null si no existe aún.

PUT/api/rrss/estrategiaNew

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

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

GET/api/rrss/ideas

Filtros: ?status=aprobado · ?platform=Instagram

POST/api/rrss/ideas
-d '{"topic":"5 errores de PYMEs en Instagram","platform":"Instagram","keyword":"marketing PYME"}'
PUT/api/rrss/ideas/{id}/status

{"status":"aprobado"}

PUT/api/rrss/ideas/{id}New

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.

DELETE/api/rrss/ideas/{id}
📄

RRSS — Posts

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

GET/api/rrss/posts

Filtros: ?status=borrador · ?platform=LinkedIn

PUT/api/rrss/posts/{id}New

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.

DELETE/api/rrss/posts/{id}New

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.

PUT/api/rrss/posts/{id}/status

Al aprobar → guarda approved_by_user_id. Al publicar → guarda fecha_publicada.

-d '{"status":"aprobado"}'
PUT/api/rrss/posts/{id}/schedule

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.

Pendiente de definir con el equipo de agentes: hoy no existe ningún aviso de cancelación. Si un post programado se despublica, se borra o se pasa a borrador, la alarma ya creada no se entera. Hace falta acordar un contrato para eso (¿el mismo webhook con una marca de cancelado, u otro?) antes de apagar el puente de respaldo.
POST/api/images/uploadNew

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

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

GET/api/rrss/metricasNew

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.

GET/api/rrss/metricas/top-posts

Top 10 posts por engagement.

GET/api/rrss/metricas/weeklyNew

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

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

GET/api/rrss/reportesNew

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":"..."}]}
GET/api/rrss/reportes/{id}New

El reporte con su html completo, para mostrarlo o compartirlo. Un reporte de otra empresa devuelve 403.

POST/api/rrss/reportes/{id}/enviarNew

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.

DELETE/api/rrss/reportes/{id}New

Borra un reporte del histórico. Un reporte de otra empresa devuelve 403.

No hay enlace público para compartir. Las rutas piden sesión, así que hoy el reporte sale de la plataforma por correo (arriba) o se ve dentro del panel. Un enlace que se pueda pegar en WhatsApp necesita una decisión aparte (URL con token y sin sesión), porque expondría datos de la empresa a quien tenga el link.
🧩

RRSS — Pipeline de agentes

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

GET/api/rrss/pipelineNew
// 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).

Cambios del 29-julNew

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 NULLCOUNT(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

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

POST/api/rrss/run-strategist

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"
}
POST/api/rrss/run-content-ai

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}'
POST/api/rrss/run-analytics

Key: rrss_analytics

n8n — Lectura general

Estos 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/*.

GET/api/n8n/companies

Lista todas las empresas.

GET/api/n8n/companies/{id}

Perfil de empresa (sin datos sensibles).

GET/api/n8n/companies/{id}/keywords

Filtros: ?estado=activa · ?origen=manual

GET/api/n8n/companies/{id}/keywords/pending-articulos

Solo keywords sin artículo.

GET/api/n8n/companies/{id}/auditorias
GET/api/n8n/companies/{id}/auditorias/{auditoria}

Detalle con HTML del reporte.

GET/api/n8n/companies/{id}/productos-servicios

n8n — Lectura RRSS

Base: /api/n8n/companies/{id}/rrss/

GET/api/n8n/companies/{id}/rrss/canales

Canales activos con handle, objetivo, frecuencia y rol en el embudo.

GET/api/n8n/companies/{id}/rrss/estrategia

null si no existe.

GET/api/n8n/companies/{id}/rrss/ideas

Solo ideas con status aprobado.

GET/api/n8n/companies/{id}/rrss/posts

Posts con status aprobado o programado.

Alias 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.
Campo 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.
GET/api/n8n/pages/{page_id}

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

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

POST/api/agent/keywords/bulk

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"}
]}'
POST/api/agent/articulos
-d '{"empresa_id":1,"articulos":[{"keyword_id":10,"titulo":"Guía de contrato","contenido_html":"<h1>...","estado":"borrador"}]}'
POST/api/agent/auditorias
POST/api/agent/auditoria-inicial

Guarda o actualiza la auditoría inicial (upsert por empresa).

🤖

Agent — Escritura RRSS

Todos los registros creados quedan con generated_by = 'n8n' automáticamente.

POST/api/agent/rrss/estrategiaNew

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"]}'
POST/api/agent/rrss/ideas

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}]}'
POST/api/agent/rrss/posts

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.

POST/api/agent/rrss/reportesNew

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.

DELETE/api/agent/rrss/posts/{id}New

Borra un post (limpieza de duplicados de pruebas). Solo borradores: un post aprobado/programado/publicado devuelve 422 con {"ok":false,"error":"..."}.

POST/api/agent/rrss/posts/{id}/mark-publishedNew

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.

POST/api/agent/rrss/metricas

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

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

PUT/api/crm/deals/{id}New

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.

POST/api/crm/deals

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
Lectura → Authorization: Bearer <N8N_TOKEN>
Escritura → Authorization: Bearer <AGENT_TOKEN>
Keys en DB — configurar en Panel › Agentes
AgenteKey
Strategist AIrrss_strategist
Content AI (ideas + post)rrss_content_ai
Analytics AIrrss_analytics
🧠

Strategist AI

Lee perfil y canales, genera estrategia RRSS completa, la guarda. El equipo puede editarla desde el panel.

Flujo
1
Webhook trigger — recibe company_id
2
GET /api/n8n/companies/{id} — nombre, industria, país
3
GET /api/n8n/companies/{id}/rrss/canales
4
GET /api/n8n/companies/{id}/rrss/estrategia — puede ser null
5
Nodo IA — genera la estrategia (prompt abajo)
6
POST /api/agent/rrss/estrategia
Prompt
Eres 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

New 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):

A
Botón "Generar Ideas"mode: "ideas" → genera ideas nuevas (sin correr el Strategist completo) → POST /api/agent/rrss/ideas
B
Botón "Generar Post"mode: "post" (opcional idea_id) → genera el borrador → POST /api/agent/rrss/posts

Payload 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

Genera 10 ideas basadas en la estrategia y canales. Llegan con status = idea para revisión del equipo.

Flujo
1
Webhook — recibe company_id, mode = "ideas"
2
GET perfil + canales + estrategia
3
Nodo IA — genera 10 ideas cubriendo todos los pilares
4
POST /api/agent/rrss/ideas — bulk, respuesta: {"ok":true,"created":10}
Prompt
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

Toma una idea aprobada y genera el post completo: copy por plataforma, hooks y CTA. Llega como borrador.

Flujo
1
Webhook — recibe company_id, mode = "post", idea_id
2
GET perfil + estrategia + idea específica
3
Nodo IA — genera copy para cada plataforma activa
4
POST /api/agent/rrss/posts — borrador con social_idea_id enlazado
Prompt
Eres 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

Lee métricas de las plataformas y las carga al backend.

Flujo
1
Webhook — recibe company_id
2
GET /api/n8n/companies/{id}/rrss/canales — handles y plataformas
3
GET /api/n8n/companies/{id}/rrss/posts — para linkear métricas por post
4
Consultar fuente de datos (ver opciones abajo)
5
POST /api/agent/rrss/metricas
Opción recomendada para arrancar: Google Sheets. El cliente llena una hoja con métricas del mes y n8n la lee con el nodo Google Sheets. Sin OAuth por empresa.
A largo plazo: Meta Graph API para Instagram/Facebook, LinkedIn Marketing API. Requiere tokens OAuth por empresa.
🗺️

Orden de construcción recomendado

#AgentePor qué primero
1Content AI — IdeasValor inmediato. El cliente ve ideas en segundos.
2Content AI — PostAmplía el anterior. Misma key, mismo workflow.
3Strategist AIRequiere canales bien configurados primero.
4Analytics AI (Sheets)Mínimo viable. Sin OAuth por empresa.
5Analytics AI (APIs)Automatización total. Iteración avanzada.
Errores comunes a evitar:
  • No enviar company_id / empresa_id en el body → 422
  • status con valor fuera del enum → 422
  • fecha_programada en el pasado → rechazada
  • Omitir Authorization header → 401
✈️

Telegram — Webhook de entrada

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

⚠️ Importante para el equipo de agentes: un bot de Telegram admite un solo webhook a la vez. Si se registra otro apuntando a n8n, el nuestro queda desregistrado y los mensajes dejan de llegar a la plataforma. El webhook lo tiene Growth54 y desde aquí se reenvía al agente (ver la sección siguiente).
POST/api/telegram/webhook/{secret}público

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

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

Si no hay agente configurado no pasa nada malo: el mensaje se guarda y se ve en el panel igual. El módulo no depende de que el agente exista, y si el agente se cae no se pierde ningún mensaje.

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

Lectura y gestión desde el panel, siempre acotado a la empresa activa del usuario.

MétodoRutaQué hace
GET/api/telegram/statsConversaciones, contactos, leads, calientes y negocios en el CRM
GET/api/telegram/conversationsLista paginada, con el último mensaje de cada una
GET/api/telegram/conversations/{id}Hilo completo
POST/api/telegram/replyResponder. Envía directo por la API de Telegram, sin n8n
GET/api/telegram/configEstado del bot. El token nunca se devuelve: solo si está puesto y sus últimos dígitos
PUT/api/telegram/configGuardar token y conectar, o {"is_active": false} para desconectar
GET/api/telegram/webhook-infoDiagnóstico: qué webhook tiene Telegram registrado ahora mismo
Diferencia con WhatsApp: allí responder depende del agente 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

Datos 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étodoRutaQué hace
GET/api/email/configCuenta guardada y valores por defecto de Gmail, Outlook y otros
PUT/api/email/configGuardar. Si secret llega vacío se conserva la contraseña anterior
POST/api/email/config/testAbre una conexión IMAP real y hace LOGIN para comprobar las credenciales
Se usa contraseña de aplicación (IMAP/SMTP), no OAuth: leer correo con OAuth obliga a pasar la revisión de seguridad de Google, el mismo muro que bloqueó la verificación de Meta. La contraseña se guarda cifrada y nunca se devuelve.
💬

WhatsApp Engine — Inbound (agente n8n)

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

POST/api/agent/whatsapp/inboundAGENT_API_TOKEN
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)

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

GET/api/n8n/companies/{id}/wa/leads?phone={phone}N8N_TOKEN

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)

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

Acción destructiva e irreversible. El borrado masivo pide {"confirm":"DELETE"} en el body como seguro anti-accidentes: evita vaciar la empresa equivocada por un {company} mal escrito.
DELETE/api/agent/companies/{company}/whatsapp/leadsagent token

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)."
}
DELETE/api/agent/companies/{company}/whatsapp/leads/{contactId}agent token

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)

Endpoints que consume el frontend para mostrar conversaciones, leads y follow-ups. Todos operan sobre la empresa activa del usuario autenticado (current_company_id).

GET/api/whatsapp/statstoken

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.

GET/api/whatsapp/conversationstoken

Lista paginada de conversaciones con datos del contacto y último mensaje. Ordenadas por updated_at descendente.

GET/api/whatsapp/conversations/{id}token

Conversación completa: datos del contacto + todos los mensajes ordenados por sent_at.

GET/api/whatsapp/followupstoken

Follow-ups activos (estado: pendiente | programado | activo) ordenados por scheduled_at. Incluye nombre y teléfono del contacto.

POST/api/whatsapp/replytoken
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.

DELETE/api/whatsapp/leads/{id}tokenNew
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{webhook wa_reply} — lo que Growth54 envía a n8nNew
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:

  1. 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 rol bot, user o sys; agent/agente devuelve 422.)
  2. Nombres de campos. El flujo debe leer phone, message, company_id y phone_number_id tal cual (no telefono, texto, mensaje ni to).
  3. Credenciales dinámicas. Resolver el token con empresa-by-phone usando el phone_number_id recibido. No cablear tokens por empresa: no escala multiempresa.
  4. Autenticación. Si el webhook exige token, avisar para definir N8N_WA_REPLY_WEBHOOK_TOKEN; hoy no se envía cabecera Authorization.

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

n8n agrega keywords a una empresa
  • GET /api/n8n/companies → obtener empresa_id
  • POST /api/agent/keywords/bulk → insertar keywords
Agente genera artículos para keywords sin contenido
  • GET /api/n8n/companies/{id}/keywords/pending-articulos
  • El agente genera HTML para cada keyword
  • POST /api/agent/articulos
Agente RRSS genera banco de ideas
  • Panel llama POST /api/rrss/run-content-ai con mode: 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_TOKEN y AGENT_API_TOKEN en .env
  • Siempre enviar: Accept: application/json y Content-Type: application/json
⚠️ Solo desarrollo: POST /api/dev/purge-current-company elimina todos los datos de la empresa activa. Nunca en producción.