API de Campañas https://api-v2.sofiachat.com
API de Campañas

Introducción

Esta referencia describe la API de Sofia Chat para operar campañas de WhatsApp de punta a punta: gestión de contactos y grupos, plantillas aprobadas por Meta, creación y envío de campañas, y seguimiento del estado de entrega de cada mensaje — todo vía API, sin necesidad de ingresar al panel.

Todos los endpoints están bajo la URL base de producción y requieren autenticación, salvo el inicio de sesión. Las operaciones actúan únicamente sobre los datos de su organización.

Flujo de integración

  • Autentíquese y obtenga su token.
  • Descubra sus recursos: organización, canal de WhatsApp, plantillas y etiquetas.
  • Cargue destinatarios: contactos y grupos, individual o por Excel.
  • Cree y envíe la campaña; consulte el avance por destinatario.
URL base
https://api-v2.sofiachat.com
Inicio rápidocURL
# Obtenga su token y llame la API
curl -X POST \
  https://api-v2.sofiachat.com/api/auth/log-in \
  -H "Content-Type: application/json" \
  -d '{"email":"api@su-empresa.com",
       "password":"***"}'

Requisito previo

WhatsApp Business enrolado en Sofia Chat La cuenta de WhatsApp Business de su empresa debe estar enrolada y activa en la plataforma antes de usar la API de campañas. El enrolamiento conecta su número con su organización y habilita el canal de envío; sin él, la creación y el envío de campañas serán rechazados. Se realiza una única vez por número — contacte al equipo de Sofia Chat para coordinarlo.

Convenciones

  • Prefijo /api en todas las rutas.
  • Mutaciones vía POST con sub-ruta semántica (/update, /delete, /opt-out…). La API no acepta PATCH, PUT ni DELETE en estos módulos.
  • Respuesta estándar { "ok": true, ... }; listados paginados: { ok, data, total, page, pageSize }.
  • Fechas ISO 8601 en UTC. Teléfonos en formato internacional, solo dígitos, 8–15 (ej. Guatemala: 50255551234).

Códigos de error

CódigoSignificado
400Solicitud inválida — revise el cuerpo.
401Token vencido o inválido — vuelva a autenticarse.
403El rol no tiene permiso sobre el módulo.
404Recurso inexistente o de otra organización.
5xxError del servidor — reintente con espera exponencial.
Respuesta paginada200
{
  "ok": true,
  "data": [  ],
  "total": 120,
  "page": 1,
  "pageSize": 20
}
Autenticación

Iniciar sesión

POST/api/auth/log-in

Obtiene el token de acceso (JWT) con las credenciales de su cuenta. Incluya el token en el encabezado Authorization: Bearer {token} de todas las llamadas siguientes.

Body
emailstringrequerido
Correo de la cuenta de API de su organización.
passwordstringrequerido
Contraseña de la cuenta.

El token tiene vigencia limitada: ante un 401, vuelva a autenticarse y reintente. Recomendamos automatizar la renovación.

RequestcURL
curl -X POST \
  https://api-v2.sofiachat.com/api/auth/log-in \
  -H "Content-Type: application/json" \
  -d '{"email":"api@su-empresa.com",
       "password":"********"}'
Response200
{
  "ok": true,
  "token": "eyJhbGciOi...",
  "refreshToken": "9f3c1a7b-..."
}
Descubrimiento

Mis organizaciones

GET/api/organization/my-organizations

Devuelve las organizaciones del usuario autenticado. El campo organizationId es el {orgId} que usará en todas las demás rutas.

Response200
{
  "ok": true,
  "organizations": [
    {
      "role": "owner",
      "organizationId": 7,
      "organization": {
        "id": 7,
        "name": "Su Empresa"
      }
    }
  ]
}
Descubrimiento

Canales de WhatsApp

GET/api/departments/organization/{orgId}/enriched

Devuelve los departamentos de su organización con sus canales. El id del canal de tipo whatsapp es el integrationId que se usa en plantillas y campañas.

Identifique el canal por departamento Por seguridad, la API no expone el número de teléfono del canal. Si su organización tiene varios números, identifíquelos por el departamento al que pertenecen.
Response200
[
  {
    "id": 3,
    "name": "Ventas",
    "channels": [
      {
        "id": 11,
        "type": "whatsapp",
        "status": "active"
      }
    ]
  }
]
Descubrimiento

Etiquetas de conversación

GET/api/conversation-tag/organization/{orgId}

Devuelve las etiquetas de su organización. Las conversaciones generadas por una campaña quedan clasificadas bajo la etiqueta indicada al crearla (conversationTagId), visibles para su equipo en el panel de Sofia Chat.

Response200
{
  "ok": true,
  "tags": [
    {
      "id": 14,
      "name": "Prioridad alta",
      "color": "#FF5733"
    }
  ]
}
Contactos

Listar contactos

GET/api/contact/{orgId}

Listado paginado de los contactos de la organización, con filtros de búsqueda. El detalle de un contacto se obtiene con GET /api/contact/{orgId}/{id}.

Query params
pagenumberopcional
Página a consultar (desde 1).
limitnumberopcional
Resultados por página.
searchstringopcional
Búsqueda por nombre, teléfono o correo.
Response200
{
  "ok": true,
  "data": [
    {
      "id": 501,
      "phone": "+50255551234",
      "first_name": "Ana",
      "last_name": "López",
      "email": "ana@ejemplo.com",
      "custom_fields": { "ciudad": "Guatemala" },
      "opt_out_at": null
    }
  ],
  "total": 1, "page": 1, "pageSize": 20
}
Contactos

Crear contacto

POST/api/contact/{orgId}
Body
phonestringrequerido
Formato internacional: código de país + número, 8–15 dígitos. La plataforma lo normaliza (elimina espacios y signos) y rechaza números inválidos con 400.
firstNamestringopcional
Nombre del contacto.
lastNamestringopcional
Apellido del contacto.
emailstringopcional
Correo electrónico.
customFieldsobjectopcional
Atributos propios de su negocio ({"ciudad": "Guatemala"}). Defina las claves primero en Campos personalizados; luego puede usarlas para personalizar mensajes de campaña.
RequestcURL
curl -X POST \
  https://api-v2.sofiachat.com/api/contact/{orgId} \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "50255551234",
    "firstName": "María",
    "customFields": { "ciudad": "Guatemala" }
  }'
Contactos

Actualizar y eliminar

POST/api/contact/{orgId}/{id}/update
POST/api/contact/{orgId}/{id}/delete

Actualiza los datos de un contacto (mismo cuerpo que la creación; los campos omitidos no cambian) o lo elimina. Recuerde: todas las mutaciones son POST con sub-ruta; PATCH/PUT/DELETE no son aceptados.

Contactos

Bajas de comunicación (opt-out / opt-in)

POST/api/contact/{orgId}/{id}/opt-out
POST/api/contact/{orgId}/{id}/opt-in

Da de baja a un contacto de las campañas, o lo reactiva. Un contacto con opt-out queda excluido automáticamente de toda campaña — aparece como skipped entre los destinatarios aunque esté en el grupo enviado — conforme a las políticas de Meta. Este estado no puede sobrescribirse mediante la importación de Excel.

RequestcURL
curl -X POST \
  https://api-v2.sofiachat.com\
/api/contact/{orgId}/{id}/opt-out \
  -H "Authorization: Bearer {token}"
Grupos y campos

Listar grupos

GET/api/contact-group/{orgId}

Devuelve los grupos de la organización con su conteo de miembros. Los grupos son la forma recomendada de dirigir campañas: segmentos reutilizables a los que apunta con su groupId.

Response200
{
  "ok": true,
  "groups": [
    {
      "id": 12,
      "name": "Clientes VIP",
      "description": "Compras > $1000",
      "memberCount": 34
    }
  ]
}
Grupos y campos

Crear y administrar grupos

POST/api/contact-group/{orgId}
POST/api/contact-group/{orgId}/{id}/update
POST/api/contact-group/{orgId}/{id}/delete

Crea un grupo con { "name": "...", "description": "..." }, actualiza sus datos o lo elimina.

Grupos y campos

Miembros del grupo

POST/api/contact-group/{orgId}/{id}/add-contacts
POST/api/contact-group/{orgId}/{id}/remove-contacts
Body
contactIdsnumber[]requerido
IDs de los contactos a agregar o quitar del grupo.
RequestcURL
curl -X POST \
  https://api-v2.sofiachat.com\
/api/contact-group/{orgId}/{id}/add-contacts \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"contactIds":[501,502]}'
Grupos y campos

Campos personalizados

GET/api/contact-field/{orgId}
POST/api/contact-field/{orgId}
POST/api/contact-field/{orgId}/{id}/update
POST/api/contact-field/{orgId}/{id}/delete

Define los atributos propios de su negocio (ciudad, empresa, número de póliza…). Una vez definidos, las claves quedan disponibles como columnas en la importación por Excel, en customFields de cada contacto, y como custom_fields.<clave> en la personalización de mensajes de campaña.

Importación Excel

Descargar plantilla de importación

GET/api/contact/{orgId}/import/template

Descarga un archivo .xlsx con los encabezados correctos — phone, first_name, last_name, email, más una columna por cada campo personalizado definido — y una fila de ejemplo. Úselo como base para preparar sus datos.

Importación Excel

Validar archivo (preview)

POST/api/contact/{orgId}/import/preview

Valida el archivo sin escribir nada en la base (dry run). Envíe el archivo como multipart/form-data en el campo file. Límite: 5,000 filas por archivo.

  • phone es la única columna obligatoria; filas sin teléfono válido se marcan como inválidas.
  • Detecta duplicados dentro del archivo y contra la base.
  • Columnas que coinciden con un campo personalizado se asignan a customFields; las desconocidas se ignoran.
Response200
{
  "totalRows": 1250,
  "valid": 1180,
  "invalid": 40,
  "duplicatesInFile": 10,
  "duplicatesInDb": 20,
  "rows": [
    {
      "rowNumber": 2,
      "phone": "50255551234",
      "status": "new"
    }
  ]
}
Importación Excel

Confirmar importación

POST/api/contact/{orgId}/import/confirm

Ejecuta la importación. Envíe el archivo (multipart, campo file) junto con las opciones:

Body (form-data)
filefile .xlsxrequerido
El mismo archivo validado en el preview.
groupIdnumberopcional
Todos los contactos importados quedan asignados a este grupo — su segmento de campaña en un solo paso.
updateExistingbooleanrequerido
Con true los duplicados se actualizan; con false se omiten.
Response200
{
  "created": 1150,
  "updated": 20,
  "skipped": 10,
  "failed": 0
}
Importación Excel

Exportar contactos

GET/api/contact/{orgId}/export?groupId=&optedOut=

Exporta los contactos de la organización a .xlsx, con filtros opcionales por grupo y por estado de opt-out. Útil para auditoría y respaldo.

Plantillas

Listar plantillas

GET/api/template/{orgId}?integrationId=

Devuelve las plantillas de la organización, con filtro opcional por canal. Solo las plantillas en estado approved pueden usarse en campañas. Las variables del mensaje ({{1}}, {{2}}…) están en el componente BODY.

pendingapprovedrejectedpauseddisabled

El detalle individual se obtiene con GET /api/template/{orgId}/{id}.

Response200
{
  "ok": true,
  "templates": [
    {
      "id": 88,
      "integration_id": 11,
      "name": "recordatorio_cita",
      "language": "es_MX",
      "category": "utility",
      "status": "approved",
      "components": [
        {
          "type": "BODY",
          "text": "Hola {{1}}, tu cita es el {{2}}"
        }
      ]
    }
  ]
}
Plantillas

Sincronizar con Meta

POST/api/template/{orgId}/sync

Sincroniza las plantillas del canal indicado desde Meta: trae plantillas nuevas y actualiza estados de aprobación o recategorizaciones. Ejecute una sincronización antes de campañas importantes.

Body
integrationIdnumberrequerido
Canal de WhatsApp cuyas plantillas se sincronizan.
Response200
{ "ok": true, "created": 3, "updated": 5, "total": 8 }
Plantillas

Crear y eliminar plantillas

POST/api/template/{orgId}/create
POST/api/template/{orgId}/{id}/delete

Crea una plantilla y la envía a aprobación de Meta (queda en pending; la aprobación toma normalmente hasta 24 horas — sincronice después para actualizar el estado), o la elimina. Meta puede recategorizar plantillas (por ejemplo, de utility a marketing); la sincronización refleja esos cambios.

Campañas

Crear campaña

POST/api/campaign/{orgId}
Body
namestringrequerido
Nombre interno de la campaña, visible en el panel.
integrationIdnumberrequerido
Canal de WhatsApp desde el que se envía (ver Canales).
templateIdnumberrequerido
Plantilla en estado approved.
conversationTagIdnumberrequerido
Etiqueta bajo la que se clasifican las conversaciones resultantes.
parameterMappingobjectrequerido
Personalización por destinatario: cada variable de la plantilla ("1", "2"…) se mapea a un campo del contacto — first_name, last_name, email, phone o custom_fields.<clave>. El mensaje de cada destinatario se arma con sus propios datos.
scheduledAtstring ISO 8601opcional
Fecha/hora de envío en UTC. Si se omite, la campaña queda en draft hasta dispararla con /send.
groupIdsnumber[]opcional
Grupos destinatarios.
contactIdsnumber[]opcional
Contactos individuales. Puede combinarse con groupIds; los duplicados se descartan automáticamente. Indique al menos uno de los dos.
RequestcURL
curl -X POST \
  https://api-v2.sofiachat.com/api/campaign/{orgId} \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Promo agosto",
    "integrationId": 11,
    "templateId": 88,
    "conversationTagId": 14,
    "parameterMapping": {
      "1": "first_name",
      "2": "custom_fields.ciudad"
    },
    "groupIds": [12]
  }'
Response200
{
  "ok": true,
  "data": {
    "id": 30,
    "name": "Promo agosto",
    "status": "draft",
    "recipientCounts": {
      "total": 34, "pending": 34
    }
  }
}
Campañas

Enviar campaña

POST/api/campaign/{orgId}/{id}/send

Dispara el envío de una campaña en draft o scheduled. El procesamiento es asíncrono: la plataforma encola los mensajes y los envía de forma progresiva, con reintentos automáticos ante errores transitorios. Consulte el avance con el detalle y los destinatarios; los estados se actualizan conforme Meta confirma cada evento (enviado → entregado → leído).

RequestcURL
curl -X POST \
  https://api-v2.sofiachat.com\
/api/campaign/{orgId}/{id}/send \
  -H "Authorization: Bearer {token}"
Campañas

Cancelar campaña

POST/api/campaign/{orgId}/{id}/cancel

Cancela una campaña programada o detiene el envío de los mensajes que aún estén pendientes. Los mensajes ya enviados no pueden revertirse.

Campañas

Listar campañas y detalle

GET/api/campaign/{orgId}?page=&limit=&status=
GET/api/campaign/{orgId}/{id}

Listado paginado con filtro opcional por estado, y detalle individual. Cada campaña incluye sus contadores de destinatarios: total, pendientes, enviados, entregados, leídos, fallidos y omitidos.

Response200
{
  "ok": true,
  "data": [
    {
      "id": 30,
      "name": "Promo agosto",
      "status": "completed",
      "parameter_mapping": {
        "1": "first_name",
        "2": "custom_fields.ciudad"
      },
      "recipientCounts": {
        "pending": 0,
        "sent": 200,
        "failed": 4
      }
    }
  ],
  "total": 1, "page": 1, "pageSize": 20
}
Campañas

Destinatarios de una campaña

GET/api/campaign/{orgId}/{id}/recipients?page=&limit=&status=

Listado paginado de destinatarios con el estado individual de cada mensaje y los datos del contacto. Filtre por status=failed después de cada campaña para depurar números inválidos de sus listas.

Response200
{
  "ok": true,
  "data": [
    {
      "id": 901,
      "status": "read",
      "contact": {
        "id": 501,
        "phone": "+50255551234",
        "first_name": "Ana"
      }
    }
  ],
  "total": 34, "page": 1, "pageSize": 50
}
Guías

Estados

Campaña

draftscheduledrunningpausedcompletedfailedcancelled

Destinatario

EstadoSignificado
pendingEn cola, aún no enviado.
sentEnviado a WhatsApp.
deliveredEntregado al dispositivo del destinatario.
readLeído por el destinatario.
failedFalló (número inválido, restricciones de Meta u otro error permanente).
skippedOmitido (por ejemplo, contacto con opt-out).

Plantilla

pendingapprovedrejectedpauseddisabled
Guías

Ambiente y pruebas controladas

La API opera sobre un único ambiente productivo: los mensajes enviados son mensajes reales de WhatsApp. Realice sus pruebas de forma controlada:

Guías

Buenas prácticas

Guías

Soporte

Para el enrolamiento de su cuenta de WhatsApp Business, la entrega de credenciales de API o cualquier duda sobre esta integración, contacte al equipo de Sofia Chat a través de su canal de atención habitual o al correo hola@sofiachat.com.