Documentación de la API de Be CRM
Bienvenido a la referencia de la API REST de Be CRM. Con ella puedes crear y actualizar leads, enviar correos, dar de baja contactos y verificar tus integraciones — todo desde tus propios sistemas.
URL base
Todas las peticiones se hacen sobre HTTPS contra:
https://crm.begraffic.com
Formato
- Las peticiones y respuestas usan JSON (
Content-Type: application/json). - Cada token está asociado a un workspace (tu
tableId), así que solo operas sobre los datos de tu propio workspace. - Las respuestas devuelven códigos HTTP estándar:
2xxéxito,400body inválido,401/403autenticación,404no encontrado,405método no permitido.
Recursos
| Recurso | Para qué sirve |
|---|---|
| Leads | Crear, buscar y actualizar contactos, sus listas y organización. |
| Correos | Enviar correos personalizados o por plantilla desde tu remitente verificado. |
| Baja (unsubscribe) | Dar de baja a un contacto desde un enlace público. |
| Webhook de verificación | Confirmar que una integración externa quedó conectada. |
Autenticación
Todas las peticiones autenticadas requieren el encabezado:
Authorization: Bearer <token>
Tipos de token
- Token de API (recomendado para integraciones): se genera en la consola y tiene el formato
bmkt_.... El servidor guarda solo un hash SHA‑256, por lo que el secreto nunca se almacena ni se expone. Cópialo en el momento de crearlo — es la única vez que lo verás completo. - ID token de sesión: el token de un usuario autenticado.
En ambos casos se valida que el token esté activo y asociado a un workspace (tableId). Si el token no existe o está deshabilitado, recibirás 401 Unauthorized.
Cómo crear un token de API
- Entra a tu consola de Be CRM.
- Ve a Ajustes → API keys.
- Crea una nueva clave, asígnale un nombre y cópiala (solo se muestra una vez).
- Úsala en el encabezado
Authorization: Bearer bmkt_....
Trata tus tokens como contraseñas: no los publiques en el frontend ni los subas a un repositorio. Si se filtra uno, elimínalo desde la consola y genera otro.
curl https://crm.begraffic.com/api/leads?email=lead@ejemplo.com \
-H "Authorization: Bearer bmkt_tu_token"
Instrucciones para administrar leads
Endpoints
- GET
/api/leads?email=correo@example.compara buscar un lead por email. - POST
/api/leadspara crear un nuevo lead. - PATCH
/api/leads/{id}o PUT/api/leads/{id}para actualizar un lead existente.
Autenticación
Incluye el header:
Authorization: Bearer <token>
El token puede ser un ID token de Firebase o un token pre-registrado en la colección tokens. En ambos casos se validará que esté activo y asociado a una tableId.
Asignación por defecto del token (API key)
Una API key puede tener configurada una asignación por defecto (en Ajustes › API keys: organización, etiquetas y/o listas). Al crear un lead nuevo con esa key:
- La organización por defecto se asigna solo si el request NO envía
organizationIdniorganizationName(lo explícito del request siempre manda). - Las etiquetas y listas por defecto se suman a las que envíes en
tags/groups. - Solo aplica al crear un lead nuevo; al actualizar un lead existente, los defaults del token no se aplican.
Campos del body (creación)
name(string, opcional)secondName(string, opcional)lastname(string, opcional)secondLastname(string, opcional)email(string, opcional): se normaliza a minúsculas y sin espacios. Debe tener formato válido si se envía.phone(objeto, opcional):{ phone_code, phone }para código de área y número fijo. Si envíasphone,phone_codees requerido y el número solo acepta dígitos y símbolos+ ( ) -.movil(objeto, opcional):{ movil_code, movil }para código y número móvil. Si envíasmovil,movil_codees requerido y el número solo acepta dígitos y símbolos+ ( ) -.address(objeto, opcional):{ address1, address2, city, state, country, zip }.gender(string, opcional): género del lead; usaMasculinooFemenino(no sensible a mayúsculas/minúsculas). Se guarda recortado.birthdate(string, Date o null, opcional): fecha de nacimiento. Acepta formatoYYYY-MM-DDo un objeto Date; se guarda como timestamp. Envíanullo cadena vacía para borrar.financialActivities(array<object>, opcional): movimientos financieros iniciales. Cada elemento aceptakind(requerido),amount(number),currency(string),description(string),status(string),date(string o Date;YYYY-MM-DD).tags(string[], opcional): etiquetas del lead. Se eliminan duplicados y espacios.groups(string[], opcional): IDs de listas a las que se vincula el lead. Se escriben en el campogroupsdel lead (es así como un lead "pertenece" a una lista). Se deduplican.organizationId(string, opcional): ID de la organización (empresa) a la que se asigna el lead. Si el ID no existe, se responde400. Al asignarla se seteanorganizationIdyorganizationName, y además se agregan las listas de la organización agroupsy sus etiquetas atags, para que el lead realmente aparezca bajo esa organización.organizationName(string, opcional): nombre de la organización. Normalmente se infiere deorganizationId; envíalo solo si quieres fijarlo manualmente, o para guardar el nombre sin un ID.active(boolean, opcional): por defectotrue.image(string, opcional): URL o referencia a la foto del lead.
Campos del body (actualización)
Se aceptan los mismos campos que en creación. Solo se actualizan las propiedades enviadas. email, tags, phone, movil, address, gender y birthdate también se normalizan (se recortan y omiten cuando quedan vacíos); gender debe ser Masculino o Femenino o se rechazará con 400. Para borrar phone, movil, address, gender o birthdate, envía el campo como null o con sus valores vacíos. Además puedes enviar removeTags (string[]) para eliminar etiquetas existentes sin necesidad de enviar el arreglo completo.
Para la asignación a listas y organización en actualización:
groups(string[]): IDs de listas que se agregan a las existentes del lead (merge).removeGroups(string[]): IDs de listas que se quitan del lead.organizationId(string): asigna la organización (con el mismo merge de listas/etiquetas descrito en creación). EnvíaorganizationId: nullo""para desasignar la organización (borraorganizationIdyorganizationName).organizationName(string): fija el nombre de la organización sin tocar el ID.
También puedes agregar una nota al lead con el campo note:
note.title(string, requerido)note.content(string, opcional)note.dueDate(string o Date, opcional): formatoYYYY-MM-DD.note.reminderAt(string o Date, opcional): formatoYYYY-MM-DD.note.tags(string[], opcional): etiquetas de la nota; se deduplican.note.type(string, opcional):noteoreminder(por defectonote).
Las notas se guardan en la subcolección tables/{tableId}/leads/{leadId}/notes con createdAt y createdBy automáticos.
Para agregar movimientos financieros, envía financialActivities (array de objetos):
kind(string, requerido)amount(number, opcional)currency(string, opcional; ej. USD, DOP, EUR)description(string, opcional)status(string, opcional)date(string o Date, opcional; formatoYYYY-MM-DD)
Se guardan en tables/{tableId}/leads/{leadId}/financialActivities con createdAt, updatedAt y createdBy automáticos. En la respuesta se incluye financialCreatedIds cuando se crean desde PATCH/PUT.
Respuestas
- 200 (GET): retorna el lead encontrado.
- 201 (POST): retorna el lead creado con su
id. - 200 (PATCH/PUT): retorna el lead actualizado.
- 400: body inválido o falta el
iden la ruta. - 401/403: problemas con el token o falta de permisos.
- 404: el lead no existe (en actualizaciones).
- 405: método no permitido.
Reglas y comportamiento
- Debes enviar al menos un método de contacto:
email,phone.phoneomovil.movil. - Si envías
phone.phoneomovil.movil, debes incluir su código (phone_codeomovil_code) y los números solo aceptan dígitos y+ ( ) -. - Si no envías email pero sí teléfono/móvil,
namese vuelve requerido para mostrar el contacto. - El
emailes opcional, pero si se envía debe tener formato válido; se guarda en minúsculas y recortado. - Al crear se incrementan los contadores de leads y se actualizan métricas relacionadas (etiquetas, grupos, organizaciones) cuando aplican.
- Si no envías
active, se marca comotrue. tagsse guardan sin duplicados, ignorando mayúsculas/minúsculas.groupscontiene IDs de listas (no nombres); son los que determinan la pertenencia del lead a una lista.- Asignar
organizationIdbusca la empresa en el workspace; si no existe se responde400. Al asignarla se guardanorganizationIdyorganizationName, y se replican las señales de pertenencia de la organización: sus listas se agregan agroupsy sus etiquetas atags(igual que cuando un lead entra por formulario). - En actualización,
groupsytagshacen merge con los valores existentes; usaremoveGroups/removeTagspara quitar elementos puntuales. - Se registra
createdBycon el email del usuario autenticado cuando está disponible.
Ejemplo Node.js (crear lead)
const fetch = require("node-fetch");
const body = {
name: "Juan",
secondName: "Carlos",
lastname: "Pérez",
secondLastname: "Gómez",
email: "juan.perez@example.com",
phone: { phone_code: "+52", phone: "5551234567" },
movil: { movil_code: "+52", movil: "5512345678" },
address: {
address1: "Av. Reforma 123",
address2: "Piso 4",
city: "Ciudad de México",
state: "CDMX",
country: "México",
zip: "01000",
},
gender: "Masculino",
birthdate: "1990-05-10",
image: "https://cdn.example.com/avatars/juan.png",
tags: ["Clientes", "Newsletter"],
groups: ["listId_abc", "listId_def"], // IDs de listas
organizationId: "companyId_123", // asigna la organización (agrega sus listas y tags)
active: true,
financialActivities: [
{
kind: "Compra",
amount: 2500,
currency: "MXN",
description: "Primer pedido",
status: "Pagado",
date: "2025-03-12",
},
],
};
fetch("https://crm.begraffic.com/api/leads", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer TU_TOKEN",
},
body: JSON.stringify(body),
})
.then((res) => res.json())
.catch(console.error);
Ejemplo Node.js (actualizar lead)
const fetch = require("node-fetch");
const body = {
tags: ["Clientes", "VIP"],
active: false,
birthdate: "1990-05-10",
phone: { phone_code: "+1", phone: "3051234567" },
address: {
city: "Miami",
country: "USA",
},
note: {
title: "Llamada de seguimiento",
content: "Preguntar por presupuesto y disponibilidad.",
dueDate: "2025-03-15",
tags: ["followup"],
},
financialActivities: [
{
kind: "Donación",
amount: 100,
currency: "USD",
status: "Pendiente",
},
],
};
fetch("https://crm.begraffic.com/api/leads/ID_DEL_LEAD", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer TU_TOKEN",
},
body: JSON.stringify(body),
})
.then((res) => res.json())
.catch(console.error);
Ejemplo Node.js (quitar tags)
const fetch = require("node-fetch");
const body = {
removeTags: ["Clientes"],
};
fetch("https://crm.begraffic.com/api/leads/ID_DEL_LEAD", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer TU_TOKEN",
},
body: JSON.stringify(body),
})
.then((res) => res.json())
.catch(console.error);
Ejemplo Node.js (asignar organización y listas)
const fetch = require("node-fetch");
const body = {
organizationId: "companyId_123", // agrega también las listas y tags de la organización
groups: ["listId_abc"], // vincular a listas adicionales por su ID
};
fetch("https://crm.begraffic.com/api/leads/ID_DEL_LEAD", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer TU_TOKEN",
},
body: JSON.stringify(body),
})
.then((res) => res.json())
.catch(console.error);
Ejemplo Node.js (quitar listas y desasignar organización)
const fetch = require("node-fetch");
const body = {
removeGroups: ["listId_abc"], // quita estas listas del lead
organizationId: null, // desasigna la organización (borra organizationId y organizationName)
};
fetch("https://crm.begraffic.com/api/leads/ID_DEL_LEAD", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer TU_TOKEN",
},
body: JSON.stringify(body),
})
.then((res) => res.json())
.catch(console.error);
Qué se puede y no se puede hacer
- ✔️ Crear leads con tags, imagen y estado personalizado.
- ✔️ Asignar el lead a listas (
groups) y a una organización (organizationId), igual que desde un formulario. - ✔️ Actualizar solo los campos necesarios usando PATCH o PUT.
- ✔️ Quitar elementos puntuales con
removeTags/removeGroups, o desasignar la organización conorganizationId: null. - ✔️ Usar tokens de Firebase o tokens gestionados en la colección
tokens. - ❌ Omitir el header
Authorization. - ❌ Enviar bodies que no sean JSON válido.
- ❌ Asignar una
organizationIdque no exista en el workspace (responde 400). - ❌ Usar métodos distintos de POST, PATCH o PUT.
Instrucciones para enviar correos (sencillos y templates)
Endpoint
POST /api/emails/sendEmail
Autenticación
Incluye el header:
Authorization: Bearer <token>
Campos del body
Envío sencillo (custom)
to(string, obligatorio): destinatario principal (solo uno)subject(string, obligatorio)htmlotext(string, al menos uno obligatorio)from(string, opcional): correo remitente específico (ver "Remitente")fromName(string, opcional)replyTo,cc,bcc(string o array, opcional)idCustomer(string, opcional)
Envío con template
to(string, obligatorio): destinatario principal (solo uno)template(objeto, obligatorio):name(string, obligatorio): nombre del template en tu proveedor de correodata(objeto, opcional): datos para el template
from(string, opcional): correo remitente específico (ver "Remitente")fromName(string, opcional)replyTo,cc,bcc,idCustomer: igual que en el envío sencillo
Remitente
- Sin
from, el correo sale desde el primer remitente verificado del workspace. - Con
from, el correo sale desde esa dirección solo si está registrada como remitente del workspace (o pertenece a un dominio verificado). También puedes usarsenderIdcon el id de la identidad registrada. - Si el remitente solicitado no está configurado, la API responde 404 con
{"error": "El remitente <correo> no está configurado en este workspace. ..."}y no envía el correo (nunca se sustituye por otro remitente). - Si está registrado pero aún sin verificar, responde 409.
Restricciones
- Solo se permite un destinatario principal en
to - Máximo 50 destinatarios en
cc,bcc,replyTo - Los correos deben ser válidos
- Si usas
template, no incluyassubject,htmlnitext - Si usas envío sencillo, no incluyas
template - El remitente debe estar verificado en el proveedor de correo configurado
- En envíos sencillos (custom), se agrega automáticamente un pie con el nombre de la empresa, un enlace a Be CRM y un link de baja
Ejemplo Node.js (fetch)
Sencillo
const fetch = require("node-fetch");
const body = {
to: "destinatario@ejemplo.com",
subject: "Asunto de prueba",
html: "<h1>Hola mundo</h1>",
fromName: "Mi Empresa",
};
fetch("https://crm.begraffic.com/api/emails/sendEmail", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer TU_TOKEN",
},
body: JSON.stringify(body),
})
.then((res) => res.json())
.catch(console.error);
Template
const fetch = require("node-fetch");
const body = {
to: "destinatario@ejemplo.com",
template: {
name: "MiTemplateCorreo",
data: {
nombre: "Juan",
producto: "CRM",
},
},
// Opcional: remitente específico (debe estar registrado en el workspace,
// si no la API responde 404 sin enviar).
from: "ventas@miempresa.com",
fromName: "Mi Empresa",
};
fetch("https://crm.begraffic.com/api/emails/sendEmail", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer TU_TOKEN",
},
body: JSON.stringify(body),
})
.then((res) => res.json())
.catch(console.error);
Qué se puede y no se puede hacer
- ✔️ Puedes enviar correos personalizados o usando templates de tu proveedor de correo.
- ✔️ Puedes incluir cc, bcc, replyTo, idCustomer si lo necesitas.
- ✔️ Puedes elegir el remitente con
from(osenderId) entre los remitentes registrados del workspace. - ❌ No puedes enviar a más de un destinatario principal.
- ❌ No puedes enviar sin asunto y cuerpo (en modo sencillo).
- ❌ No puedes enviar sin template.name (en modo template).
- ❌ No puedes enviar si el remitente no está verificado en tu proveedor de correo.
- ❌ No puedes enviar desde un
fromque no esté configurado en el workspace (la API responde 404 y no sustituye el remitente).
API de baja de correos (unsubscribe)
Endpoint
POST /api/unsubscribe
Uso
Permite dar de baja a un lead desde un enlace público. Debes enviar el tableId y al menos leadId o email.
Campos del body
tableId(string, obligatorio): ID del workspace.leadId(string, opcional): ID del lead.email(string, opcional): email del lead (normalizado a minúsculas).reason(string, opcional): motivo de baja (se guarda como nota del lead).
Respuesta exitosa
{
"status": "unsubscribed",
"leadId": "abc123",
"alreadyInactive": false,
"noteAdded": true
}
Ejemplo
const body = {
tableId: "workspace-123",
email: "lead@ejemplo.com",
reason: "Ya no deseo recibir mensajes",
};
fetch("https://crm.begraffic.com/api/unsubscribe", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
Webhook de verificación de integraciones
Este directorio contiene los endpoints que se exponen públicamente como webhooks dentro de la API de Pages de Next.js. Actualmente incluye el webhook de verificación de conexión para que plataformas externas puedan validar que su token está correctamente configurado y asignado al workspace indicado.
POST /api/webhooks/connection (también disponible como GET)
Propósito
Permite realizar un "ping" desde otra plataforma para verificar en tiempo real que la integración funciona. La respuesta incluye:
- Estado general de la verificación (
status,message,timestamp). - Información del workspace (ID, nombre y plan cuando están disponibles).
- El tipo de autenticación que se usó: token de API o sesión de usuario, con la misma metadata que ves en la consola (nombre, descripción, estado, contador de usos y fechas).
Autenticación
Debes enviar el encabezado Authorization: Bearer <token> donde <token> puede ser:
- Un ID token de Firebase (sesión de usuario autenticada).
- Un token API generado en la consola (formato
bmkt_...). El servidor calcula internamente el hash SHA‑256 y lo compara con la colección de tokens, por lo que jamás se expone el secreto.
Si el token está inactivo o no existe, recibirás 401 Unauthorized.
Solicitud de ejemplo
curl -X POST https://tu-dominio/api/webhooks/connection \
-H "Authorization: Bearer bmkt_ejemplo123"
Respuesta exitosa
{
"status": "ok",
"message": "Integración verificada correctamente.",
"timestamp": "2024-02-18T23:09:11.038Z",
"workspace": {
"id": "workspaceId",
"name": "Marketing Team",
"plan": "Pro"
},
"auth": {
"type": "token",
"tokenId": "f3d2b7...",
"name": "Webhook CRM",
"description": "Integración externa",
"active": true,
"createdBy": "admin@example.com",
"uses": 42,
"dateCreated": "2024-02-15T21:01:12.000Z",
"lastUsed": "2024-02-18T23:07:55.311Z"
}
}
Cuando la autenticación proviene de un usuario, la sección auth cambia a:
{
"type": "user",
"email": "usuario@example.com"
}
Errores comunes
| Código | Causa | Observaciones |
|---|---|---|
| 401 | Falta el encabezado Authorization | Incluye siempre Bearer <token> |
| 401 | Token inválido o deshabilitado | Revisa que el token exista y siga activo |
| 403 | Usuario autenticado sin tableId | La cuenta debe estar asociada a un workspace |
| 405 | Método no permitido | Solo se aceptan GET o POST |
Implementaciones recomendadas
- Usa este webhook como parte del flujo de alta de integraciones: después de guardar un token en otra plataforma, llama al endpoint y valida que
status === "ok". - Registra el
tokenId(hash) que devuelve la respuesta para facilitar auditorías sin almacenar el secreto completo.
Cualquier webhook adicional debe documentarse en este mismo archivo para mantener una referencia centralizada de los endpoints públicos.