artificialicartificialic/Docs

Primeros pasos

IntroducciónAntes de armar algoAntes de automatizarPrimeros pasosApp móvil
Nuevo
GuíasPlaybooks
Nuevo
FacturaciónChangelog

Plataforma

ConversacionesTablero
Nuevo
Seguimiento automáticoContactosCanalesLlamadas y voz
Nuevo
Web ChatComentarios
Nuevo
PlantillasDifusiones
Nuevo
EstadísticasEtiquetasAgendaEquipos y visibilidad
Nuevo
OrganizaciónAjustes

Avanzado

Asistente IA (ARIA)AutomatizacionesPlantillas de agentesVariablesAgentes IABase de ConocimientoUso de IABases de datos y vistas
Nuevo
Dashboards
Nuevo
Plantillas de negocio
Nuevo
Plugins e IntegracionesAPI RESTServidor MCP

Tutoriales

Todos los tutorialesAutomatización básicaCrear una plantillaFlujo con condicionesAgente IA con KBCrear una difusión

Soporte

Preguntas frecuentesCódigos de error de Meta
Documentación/Avanzado/API REST

API REST

La API REST de Artificialic te permite integrar tu plataforma de mensajería con sistemas externos como CRMs, bots, ERPs o cualquier aplicación que necesite enviar mensajes o gestionar contactos programáticamente.

Esta página es para desarrolladores. Asume que sabes qué es una request HTTP, cómo enviar headers de autenticación y cómo parsear JSON. Si no eres quien va a programar la integración, pásale este link a tu equipo técnico — ellos van a saber qué hacer con esto.

Alcance actual: la API v1 cubre canales, contactos, mensajes (texto, plantillas y media), conversaciones, plantillas, difusiones, etiquetas, carpetas y webhooks salientes — 45 operaciones en total. Lo que todavía se gestiona solo desde el panel: automatizaciones (más allá de dispararlas por webhook), agentes de IA y base de conocimiento.

En esta página

  • Autenticación
  • Scopes (Permisos)
  • Modo de envío
  • Canales de la clave
  • Límites e idempotencia
  • Webhooks salientes
  • Referencia completa (OpenAPI)
  • Formato de respuestas
  • Todas las operaciones
  • Endpoints — Canales
  • Endpoints — Contactos
  • Endpoints — Mensajes
  • Errores comunes
  • Ejemplos de integración

Autenticación

Todas las solicitudes a la API deben incluir un header Authorization con una clave de API válida:

Authorization: Bearer ak_tu_clave_aqui

Obtener una clave de API

  1. Ve a Configuración → API en tu dashboard.
  2. Haz clic en Crear clave.
  3. Asigna un nombre descriptivo (ej. "Integración CRM") y selecciona los permisos necesarios.
  4. Copia la clave inmediatamente. Solo se muestra una vez. Si la pierdes, deberás crear una nueva.

Características de seguridad

  • Las claves tienen el prefijo ak_ seguido de 64 caracteres hexadecimales.
  • Se almacenan como un hash SHA-256 — ni siquiera nosotros podemos ver tu clave.
  • Puedes revocar una clave en cualquier momento desde el dashboard. La revocación es inmediata.
  • Cada clave registra la fecha de último uso para que puedas auditar el acceso.

Importante: Nunca expongas tu clave de API en código del lado del cliente (frontend, apps móviles). Úsala solo desde tu servidor backend.

Scopes (Permisos)

Cada clave de API tiene uno o más scopes que determinan qué operaciones puede realizar. Asigna solo los permisos que necesites (principio de menor privilegio).

ScopeQué permiteOperaciones
channels:readLeer canales
GET /api/v1/channels
GET /api/v1/channels/{id}
GET /api/v1/channels/{id}/health
contacts:readLeer contactos
GET /api/v1/contacts
GET /api/v1/contacts/{id}
contacts:writeCrear y editar contactos
POST /api/v1/contacts
PATCH /api/v1/contacts/{id}
messages:sendEnviar mensajes y plantillas
POST /api/v1/messages
POST /api/v1/messages/{id}/retry
messages:readLeer mensajes y consultar estado de entrega
GET /api/v1/messages/{id}/status
GET /api/v1/conversations/{id}/messages
automations:triggerDisparar automatizaciones (webhooks)POST /api/hooks/{token} — el trigger "webhook" de una automatización, cuando el nodo exige clave de API
contacts:deleteEliminar contactos (borra su historial)
DELETE /api/v1/contacts/{id}
media:sendEnviar imágenes, audio y documentos
POST /api/v1/messages/media
conversations:readLeer conversaciones
GET /api/v1/conversations
GET /api/v1/conversations/{id}
conversations:writeCrear conversaciones, asignar, archivar y cambiar el modo
POST /api/v1/conversations
PATCH /api/v1/conversations/{id}
templates:readLeer plantillas
GET /api/v1/templates
GET /api/v1/templates/{id}
templates:writeCrear, sincronizar y eliminar plantillas
POST /api/v1/templates
DELETE /api/v1/templates/{id}
POST /api/v1/templates/sync
labels:readLeer etiquetas
GET /api/v1/labels
labels:writeCrear, aplicar y eliminar etiquetas
POST /api/v1/conversations/{id}/labels
DELETE /api/v1/conversations/{id}/labels
POST /api/v1/labels
DELETE /api/v1/labels/{id}
folders:readLeer carpetas
GET /api/v1/folders
folders:writeCrear, editar y eliminar carpetas
POST /api/v1/folders
PATCH /api/v1/folders/{id}
DELETE /api/v1/folders/{id}
broadcasts:readLeer difusiones y sus envíos
GET /api/v1/broadcasts
GET /api/v1/broadcasts/{id}
GET /api/v1/broadcasts/{id}/runs
POST /api/v1/broadcasts/preview-audience
broadcasts:writeCrear y editar difusiones
POST /api/v1/broadcasts
broadcasts:executeLanzar, pausar y cancelar difusiones
POST /api/v1/broadcasts/{id}/{action}
webhooks:manageGestionar webhooks salientes
GET /api/v1/webhooks
POST /api/v1/webhooks
PATCH /api/v1/webhooks/{id}
DELETE /api/v1/webhooks/{id}
POST /api/v1/webhooks/{id}/test
GET /api/v1/webhooks/{id}/deliveries
POST /api/v1/webhooks/{id}/reset-failures
POST /api/v1/webhooks/deliveries/{deliveryId}/retry

Nota: eliminar contactos exige su propio scope contacts:delete, separado de contacts:write a propósito: el borrado arrastra en cascada las conversaciones del contacto y todo su historial de mensajes. Lo mismo con las difusiones: crear un borrador (broadcasts:write) es inofensivo, lanzarlo (broadcasts:execute) envía miles de mensajes reales.

Modo de envío

Cuando un mensaje sale por la API hay que decidir a quién se le atribuye. No es cosmético: define si la conversación queda marcada como atendida por un humano, lo que detiene los bots y las automatizaciones de ese hilo.

Cada clave tiene su modo, configurable en Configuración → API:

  • API REST (por defecto): el mensaje se atribuye a la clave. No toma la conversación, no frena las automatizaciones y no se asigna a nadie. En el panel se ve como API REST · nombre de la clave. Es lo que quieres para avisos, confirmaciones y recordatorios que manda tu sistema.
  • Como agente humano: sale a nombre de un miembro de tu equipo, toma el control de la conversación y pausa los bots. Úsalo solo cuando tu sistema es la interfaz por la que responde una persona.

Se puede des-escalar por solicitud mandando sendMode: "via_api" en el cuerpo. Lo contrario no: una clave en modo API REST que pida as_user recibe 403 SEND_MODE_NOT_ALLOWED. Escalar es decisión del dueño de la clave, no de quien llama.

Canales de la clave

Por defecto una clave accede a todos los canales de tu organización, incluidos los que conectes después. Puedes restringirla desde el panel: a partir de ahí, usar un canal fuera de la lista devuelve 403 CHANNEL_NOT_ALLOWED, y las lecturas también se acotan.

Al enviar, el canal se resuelve así: si mandas channelId se usa ese; si mandas channelType gana el canal por defecto de la clave, y si hay varios candidatos se elige el más antiguo de forma determinista avisándolo en el header X-Channel-Resolution. Si no mandas ninguno, se usa el canal por defecto. Si prefieres que la ambigüedad sea un error, configura la clave para exigir canal explícito.

Límites e idempotencia

120 solicitudes por minuto por clave (ajustable según tu plan), con sub-límites más estrictos para envíos, borrados y difusiones. El cuerpo máximo es 256 KB. Al superar un límite respondemos 429 con Retry-After y los headers X-RateLimit-*.

Si se te corta la conexión antes de recibir la respuesta, no sabes si el mensaje salió. Manda un header Idempotency-Key único por operación: si reintentas con la misma clave y el mismo cuerpo, te devolvemos la respuesta original con Idempotency-Replayed: true en vez de enviar de nuevo.

curl -X POST "$BASE/api/v1/messages" \
  -H "Authorization: Bearer ak_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"channelId":"...","phoneNumber":"5215512345678","content":"Hola"}'

Webhooks salientes

En vez de preguntarnos cada X segundos si pasó algo, registra una URL y te avisamos nosotros. Se configuran en Configuración → API → Webhooks. Los eventos se suscriben uno por uno: mensaje entrante, cambio de estado de entrega, conversación nueva, cambio de asignación, cambio de modo de atención y difusión finalizada.

Tu URL es pública, así que cualquiera podría mandarle eventos falsos: por eso cada entrega va firmada con el secreto que te damos al crear el webhook (se muestra una sola vez). El header X-Artificialic-Signature tiene el formato t=<unix>,v1=<hmac>, y lo firmado es timestamp.cuerpo — verifica sobre el cuerpo crudo, antes de parsear el JSON.

Cada entrega trae además estos headers:

  • X-Artificialic-Event — tipo de evento, para rutear sin parsear el cuerpo.
  • X-Artificialic-Delivery — id único de la entrega: úsalo para deduplicar reintentos.
  • X-Artificialic-Attempt — número de intento (1 = primero).

Verificar la firma

// Node.js — verifica sobre el cuerpo CRUDO, no el JSON re-serializado
import { createHmac, timingSafeEqual } from "crypto";

function verifySignature(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((p) => p.split("="))
  );

  // Anti-replay: rechaza firmas de hace más de 5 minutos
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;

  const expected = createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1 ?? "");
  return a.length === b.length && timingSafeEqual(a, b);
}

Responde 2xx para confirmar. Ante 5xx o 429 reintentamos hasta 5 veces con espera creciente; otros 4xx no se reintentan. Si tu endpoint falla 20 eventos seguidos lo desactivamos y te lo mostramos en el panel.

Referencia completa (OpenAPI)

Esta página cubre los conceptos y los endpoints más usados. La referencia exhaustiva está en la especificación OpenAPI 3.1, que puedes importar en Postman, Insomnia o un generador de SDK:

GET /api/v1/openapi.json

Es pública y no requiere autenticación: describe la forma de la API, no datos de nadie. Ahí están también conversaciones, media, plantillas, difusiones, etiquetas, carpetas y webhooks.

Formato de respuestas

Todas las respuestas son JSON. Las respuestas exitosas envuelven los datos en un campo data:

// Respuesta exitosa
{
  "data": {
    "id": "clxyz...",
    "name": "Juan Pérez",
    ...
  }
}

// Lista con paginación
{
  "data": [ ... ],
  "nextCursor": "clxyz..." // null si no hay más resultados
}

Las respuestas de error usan un campo error con un código máquina y un mensaje legible:

// Respuesta de error
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "API key inválida o revocada"
  }
}

Códigos HTTP

CódigoSignificado
200Operación exitosa
201Recurso creado exitosamente
400Error de validación en los datos enviados
401Clave de API faltante, inválida o revocada
403Scope insuficiente o plan sin acceso a API
404Recurso no encontrado
409Conflicto (ej. contacto duplicado)
500Error interno del servidor

Todas las operaciones

La lista completa de endpoints, con el permiso que exige cada uno. Las secciones siguientes profundizan en los más usados — parámetros, respuestas y ejemplos. Para el detalle exhaustivo de cualquiera de los demás, la especificación OpenAPI los describe todos y se puede importar en Postman o en un generador de SDK.

Canales

OperaciónQué hacePermiso
GET
/api/v1/channels
Listar canaleschannels:read
GET
/api/v1/channels/{id}
Detalle de un canalchannels:read
GET
/api/v1/channels/{id}/health
Salud del canal (calidad, tier, problemas de cuenta)channels:read

Contactos

OperaciónQué hacePermiso
GET
/api/v1/contacts
Listar contactoscontacts:read
POST
/api/v1/contacts
Crear contactocontacts:write
GET
/api/v1/contacts/{id}
Detalle de contacto con sus conversacionescontacts:read
PATCH
/api/v1/contacts/{id}
Editar contactocontacts:write
DELETE
/api/v1/contacts/{id}
Eliminar contactocontacts:delete

Mensajes

OperaciónQué hacePermiso
POST
/api/v1/messages
Enviar un mensaje o una plantillamessages:send
POST
/api/v1/messages/media
Enviar imagen, audio, video o documentomedia:send
GET
/api/v1/messages/{id}/status
Estado de entrega de un mensajemessages:read
POST
/api/v1/messages/{id}/retry
Reintentar un mensaje fallidomessages:send

Conversaciones

OperaciónQué hacePermiso
GET
/api/v1/conversations
Listar conversacionesconversations:read
POST
/api/v1/conversations
Abrir una conversación con un contactoconversations:write
GET
/api/v1/conversations/{id}
Detalle de conversaciónconversations:read
PATCH
/api/v1/conversations/{id}
Archivar, asignar, mover de carpeta, marcar leída o cambiar el modoconversations:write
GET
/api/v1/conversations/{id}/messages
Mensajes de una conversaciónmessages:read
POST
/api/v1/conversations/{id}/labels
Aplicar una etiqueta a la conversaciónlabels:write
DELETE
/api/v1/conversations/{id}/labels
Quitar una etiqueta de la conversaciónlabels:write

Plantillas

OperaciónQué hacePermiso
GET
/api/v1/templates
Listar plantillas de un canaltemplates:read
POST
/api/v1/templates
Crear plantilla en Metatemplates:write
GET
/api/v1/templates/{id}
Detalle de plantillatemplates:read
DELETE
/api/v1/templates/{id}
Eliminar plantillatemplates:write
POST
/api/v1/templates/sync
Sincronizar el estado de las plantillas con Metatemplates:write

Difusiones

OperaciónQué hacePermiso
GET
/api/v1/broadcasts
Listar difusionesbroadcasts:read
POST
/api/v1/broadcasts
Crear difusiónbroadcasts:write
GET
/api/v1/broadcasts/{id}
Detalle de difusión con su última corridabroadcasts:read
GET
/api/v1/broadcasts/{id}/runs
Corridas de una difusiónbroadcasts:read
POST
/api/v1/broadcasts/{id}/{action}
Lanzar, pausar, reanudar o cancelar una difusiónbroadcasts:execute
POST
/api/v1/broadcasts/preview-audience
Cuántos destinatarios tendría una difusión, sin crearlabroadcasts:read

Etiquetas

OperaciónQué hacePermiso
GET
/api/v1/labels
Listar etiquetaslabels:read
POST
/api/v1/labels
Crear etiquetalabels:write
DELETE
/api/v1/labels/{id}
Eliminar etiquetalabels:write

Carpetas

OperaciónQué hacePermiso
GET
/api/v1/folders
Listar carpetasfolders:read
POST
/api/v1/folders
Crear carpetafolders:write
PATCH
/api/v1/folders/{id}
Editar carpetafolders:write
DELETE
/api/v1/folders/{id}
Eliminar carpetafolders:write

Webhooks salientes

OperaciónQué hacePermiso
GET
/api/v1/webhooks
Listar webhooks salienteswebhooks:manage
POST
/api/v1/webhooks
Registrar un webhook salientewebhooks:manage
PATCH
/api/v1/webhooks/{id}
Editar o pausar un webhookwebhooks:manage
DELETE
/api/v1/webhooks/{id}
Eliminar un webhookwebhooks:manage
POST
/api/v1/webhooks/{id}/test
Enviar un evento de prueba y ver la respuesta realwebhooks:manage
GET
/api/v1/webhooks/{id}/deliveries
Historial de entregas: qué se mandó y qué respondió tu servidorwebhooks:manage
POST
/api/v1/webhooks/{id}/reset-failures
Reiniciar el contador de fallos consecutivoswebhooks:manage
POST
/api/v1/webhooks/deliveries/{deliveryId}/retry
Reintentar una entrega fallidawebhooks:manage

Endpoints — Canales

GET
/api/v1/channels

Retorna la lista de canales conectados a tu organización. No incluye credenciales de acceso.

Scope requerido: channels:read

Query params

ParámetroTipoRequeridoDescripción
typestringNoFiltrar por tipo: WHATSAPP, MESSENGER, INSTAGRAM, WEBCHAT

Respuesta

{
  "data": [
    {
      "id": "cm1abc...",
      "name": "WhatsApp Business",
      "type": "WHATSAPP",
      "status": "CONNECTED",
      "profilePictureUrl": "https://...",
      "createdAt": "2026-01-15T10:30:00.000Z"
    }
  ]
}

Ejemplo cURL

curl -H "Authorization: Bearer ak_tu_clave" \
  https://tu-dominio.com/api/v1/channels

Endpoints — Contactos

GET
/api/v1/contacts

Lista los contactos de tu organización con paginación basada en cursor.

Scope requerido: contacts:read

Query params

ParámetroTipoRequeridoDescripción
searchstringNoBuscar por nombre, teléfono, email o ID externo (case-insensitive)
cursorstringNoID del último contacto de la página anterior (para paginación)
limitnumberNoContactos por página (default: 20, máximo: 100)

Respuesta

{
  "data": [
    {
      "id": "cm1def...",
      "name": "Juan Pérez",
      "phoneNumber": "584121234567",
      "externalId": null,
      "email": "[email protected]",
      "avatarUrl": null,
      "createdAt": "2026-02-10T08:00:00.000Z"
    }
  ],
  "nextCursor": "cm1ghi..."  // null si no hay más páginas
}

Paginación

La API usa paginación por cursor. Para obtener la siguiente página, pasa el valor de nextCursor como parámetro cursor:

# Primera página
GET /api/v1/contacts?limit=20

# Siguiente página
GET /api/v1/contacts?limit=20&cursor=cm1ghi...

Ejemplo cURL

curl -H "Authorization: Bearer ak_tu_clave" \
  "https://tu-dominio.com/api/v1/contacts?search=juan&limit=10"

GET
/api/v1/contacts/:id

Obtiene un contacto con todas sus conversaciones. Esto es clave para saber qué channelId usar al enviar un mensaje.

Scope requerido: contacts:read

Path params

ParámetroTipoRequeridoDescripción
idstringSíID del contacto

Respuesta

{
  "data": {
    "id": "cm1def...",
    "name": "Juan Pérez",
    "phoneNumber": "584121234567",
    "externalId": null,
    "email": "[email protected]",
    "avatarUrl": null,
    "metadata": null,
    "createdAt": "2026-02-10T08:00:00.000Z",
    "conversations": [
      {
        "id": "cm1conv...",
        "status": "OPEN",
        "channelId": "cm1ch...",
        "lastMessageAt": "2026-03-20T14:00:00.000Z",
        "channel": {
          "name": "WhatsApp Business",
          "type": "WHATSAPP"
        }
      }
    ]
  }
}

El array conversations te permite identificar en qué canales tiene conversaciones activas este contacto. Usa el channelId junto con el id del contacto para enviar mensajes.

Ejemplo cURL

curl -H "Authorization: Bearer ak_tu_clave" \
  https://tu-dominio.com/api/v1/contacts/cm1def...

POST
/api/v1/contacts

Crea un nuevo contacto en tu organización.

Scope requerido: contacts:write

Body (JSON)

ParámetroTipoRequeridoDescripción
namestringNoNombre del contacto
phoneNumberstringNoTeléfono en formato internacional (ej. +584121234567)
emailstringNoCorreo electrónico

Nota: Se requiere al menos name o phoneNumber. El número de teléfono se valida como formato E.164 internacional y se almacena sin el signo + (ej. 584121234567).

Respuesta (201 Created)

{
  "data": {
    "id": "cm1new...",
    "name": "María García",
    "phoneNumber": "584121234567",
    "externalId": null,
    "email": null,
    "createdAt": "2026-03-23T10:00:00.000Z"
  }
}

Errores específicos

HTTPCódigoCausa
400BAD_REQUESTFalta name y phoneNumber, o teléfono inválido
409CONFLICTYa existe un contacto con ese número de teléfono

Ejemplo cURL

curl -X POST \
  -H "Authorization: Bearer ak_tu_clave" \
  -H "Content-Type: application/json" \
  -d '{"name":"María García","phoneNumber":"+584121234567"}' \
  https://tu-dominio.com/api/v1/contacts

PATCH
/api/v1/contacts/:id

Actualiza uno o más campos de un contacto existente. Solo envía los campos que deseas modificar.

Scope requerido: contacts:write

Path params

ParámetroTipoRequeridoDescripción
idstringSíID del contacto

Body (JSON, todos opcionales)

ParámetroTipoRequeridoDescripción
namestringNoNuevo nombre
phoneNumberstringNoNuevo teléfono
emailstringNoNuevo email

Respuesta

{
  "data": {
    "id": "cm1def...",
    "name": "Juan A. Pérez",
    "phoneNumber": "584121234567",
    "externalId": null,
    "email": "[email protected]",
    "createdAt": "2026-02-10T08:00:00.000Z"
  }
}

Ejemplo cURL

curl -X PATCH \
  -H "Authorization: Bearer ak_tu_clave" \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]"}' \
  https://tu-dominio.com/api/v1/contacts/cm1def...

DELETE
/api/v1/contacts/:id

Elimina un contacto y todas sus conversaciones asociadas. Esta acción es irreversible.

Scope requerido: contacts:delete — scope aparte de contacts:write, porque el borrado arrastra en cascada las conversaciones del contacto y todo su historial de mensajes. Una clave con solo contacts:write recibe 403.

Path params

ParámetroTipoRequeridoDescripción
idstringSíID del contacto a eliminar

Respuesta

{
  "success": true
}

Ejemplo cURL

curl -X DELETE \
  -H "Authorization: Bearer ak_tu_clave" \
  https://tu-dominio.com/api/v1/contacts/cm1def...

Endpoints — Mensajes

POST
/api/v1/messages

Envía un mensaje de texto o una plantilla a un contacto. Acepta múltiples formas de identificar al contacto y al canal. Si el contacto o la conversación no existen, se crean automáticamente — salvo que lo apagues con createContactIfMissing o en la configuración de la clave.

Scope requerido: messages:send

Identificación del contacto (al menos uno requerido)

ParámetroTipoRequeridoDescripción
contactIdstringNoID de un contacto existente
phoneNumberstringNoNúmero de teléfono con código de país (ej. +584121234567). Solo para canales WHATSAPP. Auto-crea contacto si no existe.
facebookIdstringNoID de Facebook/Messenger del usuario. Solo para canales MESSENGER. Auto-crea contacto si no existe.
instagramIdstringNoID de Instagram del usuario. Solo para canales INSTAGRAM. Auto-crea contacto si no existe.

Identificación del canal (al menos uno requerido)

ParámetroTipoRequeridoDescripción
channelIdstringNoID de un canal específico
channelTypestringNoTipo de canal: WHATSAPP, MESSENGER, INSTAGRAM, WEBCHAT. Si hay varios conectados de ese tipo gana el canal por defecto de la clave; si no hay, el más antiguo de forma determinista, y la respuesta lo avisa en meta.warnings y en el header X-Channel-Resolution.

Si no mandas ninguno de los dos, se usa el canal por defecto de la clave. Si tampoco hay, la respuesta es 400 CHANNEL_REQUIRED. Ver Canales de la clave.

Contenido del mensaje (exactamente uno requerido)

ParámetroTipoRequeridoDescripción
contentstringNoTexto plano del mensaje
templateobjectNoPlantilla a enviar (ver estructura abajo)
template.idstringSíID de la plantilla. Para message_template puedes usar el cuid interno (cm...) o el ID numérico que te da Meta.
template.typestringSí"message_template" (Meta/WhatsApp) o "generic_template" (plantilla genérica de la organización)
template.variablesobjectNoVariables de sustitución: {"1": "valor", "2": "valor"}

Tipos de plantilla:

  • message_template — Plantilla de WhatsApp (Meta). Debe estar aprobada. Solo funciona en canales WHATSAPP. Se envía como mensaje de plantilla nativo.
  • generic_template — Plantilla genérica de tu organización. Funciona en cualquier canal. Las variables se sustituyen y se envía como texto plano.

Comportamiento del envío (opcionales)

ParámetroTipoRequeridoDescripción
sendModestringNo"via_api" o "as_user". Solo permite DES-escalar: una clave en modo humano puede mandar "via_api", pero una clave en modo API que pida "as_user" recibe 403 SEND_MODE_NOT_ALLOWED.
sendAsUserIdstringNoSolo con sendMode "as_user": miembro a nombre del que sale el mensaje.
createContactIfMissingbooleanNoPor defecto true. Con false, si el destinatario no existe la respuesta es 404 CONTACT_NOT_FOUND y no se crea nada. Mandar true contra una clave que lo tiene apagado devuelve 403 CONTACT_CREATION_NOT_ALLOWED.

Flujo interno

  1. Se resuelve el canal (por ID, por tipo, o el canal por defecto de la clave) y se valida contra su lista de canales permitidos.
  2. Se resuelve o crea el contacto (por ID, teléfono, facebookId o instagramId).
  3. Se crea la conversación si no existe (upsert).
  4. Se verifica la ventana de conversación (ej. 24h para WhatsApp — las plantillas no requieren ventana abierta).
  5. Se crea el mensaje con estado PENDING.
  6. Se envía al proveedor del canal.
  7. Se actualiza el estado a SENT o DELIVERED.
  8. Se emite un evento SSE para actualización en tiempo real.

Respuesta

Con el modo API REST (el de fábrica) el mensaje se persiste como SYSTEM sin usuario asociado: no toma la conversación ni frena las automatizaciones. En el panel se ve como API REST · <nombre de la clave>. Con as_user el senderType es AGENT y sender trae al miembro.

{
  "data": {
    "id": "cm1msg...",
    "conversationId": "cm1conv...",
    "senderType": "SYSTEM",
    "senderId": null,
    "contentType": "TEXT",
    "content": "Hola, tu pedido está listo",
    "status": "SENT",
    "externalId": "wamid.abc...",
    "createdAt": "2026-03-23T10:30:00.000Z",
    "updatedAt": "2026-03-23T10:30:01.000Z",
    "sender": null
  },
  "meta": {
    "channel": { "id": "cm1ch...", "name": "Ventas", "type": "WHATSAPP" }
  }
}

Errores específicos

HTTPCódigoCausa
400BAD_REQUESTFalta identificador de contacto o canal
400BAD_REQUESTphoneNumber solo válido para WHATSAPP / facebookId para MESSENGER / instagramId para INSTAGRAM
400BAD_REQUESTmessage_template solo funciona en canales WHATSAPP
400BAD_REQUESTPlantilla no aprobada / Ventana de conversación expirada
400CHANNEL_REQUIREDNo mandaste canal y la clave no tiene uno por defecto
404CHANNEL_NOT_FOUNDEl canal no existe (o es de otra organización)
403CHANNEL_NOT_ALLOWEDLa clave está limitada a otros canales. error.details trae los permitidos.
409CHANNEL_DISCONNECTEDEl canal existe pero está desconectado
409AMBIGUOUS_CHANNELHay varios canales de ese tipo y la clave exige canal explícito. error.details lista los candidatos.
404CONTACT_NOT_FOUNDEl destinatario no existe y no se puede dar de alta
403CONTACT_CREATION_NOT_ALLOWEDPediste createContactIfMissing: true con una clave que lo tiene apagado
403SEND_MODE_NOT_ALLOWEDPediste as_user con una clave en modo API REST
404NOT_FOUNDContacto o plantilla no encontrada
424PROVIDER_ERROREl proveedor (Meta/WhatsApp) rechazó el envío. El campo error.message contiene la razón (token expirado, sin permisos, número inválido, etc.). El mensaje se persiste en DB con status FAILED.

Ventana de conversación: En WhatsApp, solo puedes enviar mensajes de texto libre dentro de las 24 horas posteriores al último mensaje del contacto. Después de ese período, usa una message_template aprobada para reabrir la conversación. Las plantillas genéricas (generic_template) se envían como texto y están sujetas a la misma restricción de ventana.

Ejemplo 1: Texto plano por número de teléfono

curl -X POST \
  -H "Authorization: Bearer ak_tu_clave" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+584121234567",
    "channelType": "WHATSAPP",
    "content": "Hola, tu pedido está listo para retiro."
  }' \
  https://tu-dominio.com/api/v1/messages

Ejemplo 2: Template de WhatsApp

curl -X POST \
  -H "Authorization: Bearer ak_tu_clave" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+584121234567",
    "channelType": "WHATSAPP",
    "template": {
      "id": "cm1tmpl...",
      "type": "message_template",
      "variables": { "1": "Juan", "2": "#12345" }
    }
  }' \
  https://tu-dominio.com/api/v1/messages

Ejemplo 3: Template genérica

curl -X POST \
  -H "Authorization: Bearer ak_tu_clave" \
  -H "Content-Type: application/json" \
  -d '{
    "contactId": "cm1def...",
    "channelId": "cm1ch...",
    "template": {
      "id": "cm1gen...",
      "type": "generic_template",
      "variables": { "1": "Juan", "2": "#12345" }
    }
  }' \
  https://tu-dominio.com/api/v1/messages

Ejemplo 4: Retrocompatible (contactId + channelId + content)

curl -X POST \
  -H "Authorization: Bearer ak_tu_clave" \
  -H "Content-Type: application/json" \
  -d '{
    "contactId": "cm1def...",
    "channelId": "cm1ch...",
    "content": "Hola, tu pedido está listo."
  }' \
  https://tu-dominio.com/api/v1/messages

GET
/api/v1/messages/:id/status

Consulta el estado de entrega actual de un mensaje. La respuesta inicial del POST /api/v1/messages indica solo que el mensaje fue encolado en el proveedor (status SENT); los estados posteriores (DELIVERED, READ, FAILED) llegan de manera asíncrona a través de webhooks. Este endpoint te permite consultar el estado más reciente sin tener que escuchar webhooks.

Scope requerido: messages:read

Parámetros de URL

ParámetroTipoRequeridoDescripción
idstringSíID del mensaje. Acepta el cuid interno (cm...) o el externalId del proveedor (en WhatsApp es el wamid devuelto al enviar).

Respuesta

{
  "data": {
    "id": "cm1msg...",
    "externalId": "wamid.HBg...",
    "status": "DELIVERED",
    "errorMessage": null,
    "contentType": "TEMPLATE",
    "conversationId": "cm1conv...",
    "createdAt": "2026-03-23T10:30:00.000Z",
    "updatedAt": "2026-03-23T10:30:05.123Z"
  }
}

Estados posibles

StatusSignificado
PENDINGEl mensaje se creó pero aún no se envió al proveedor.
SENTEl proveedor (Meta, etc.) aceptó el mensaje y lo encoló para entrega.
DELIVEREDEl mensaje llegó al dispositivo del destinatario.
READEl destinatario abrió el chat y vio el mensaje (si tiene acuses de lectura activados).
FAILEDEl proveedor rechazó la entrega. El campo errorMessage contiene la razón.

Errores específicos

HTTPCódigoCausa
403FORBIDDENLa API key no tiene scope messages:read
404NOT_FOUNDEl mensaje no existe o pertenece a otra organización

Ejemplo: consultar por wamid

curl -H "Authorization: Bearer ak_tu_clave" \
  https://tu-dominio.com/api/v1/messages/wamid.HBgMNTg.../status

Ejemplo: consultar por cuid interno

curl -H "Authorization: Bearer ak_tu_clave" \
  https://tu-dominio.com/api/v1/messages/cm1msg.../status

POST
/api/v1/messages/media

Envía una imagen, audio, video o documento a una conversación existente. El archivo viaja en base64 dentro del JSON (no multipart): mantiene el mismo contrato que el resto de la API — JSON + Bearer — y las mismas defensas de tamaño.

Scope requerido: media:send

Body (JSON)

ParámetroTipoRequeridoDescripción
conversationIdstringSíConversación destino. Lo devuelve el POST /api/v1/messages (campo conversationId), GET /api/v1/conversations, o POST /api/v1/conversations para abrir una nueva.
mediaTypestringSí"image", "audio", "video" o "document"
datastringSíEl archivo codificado en base64
mimeTypestringSíContent-type real del archivo (ej. image/jpeg, application/pdf)
filenamestringNoNombre del archivo (relevante para documentos)
captionstringNoTexto que acompaña al medio

Tope de tamaño: el cuerpo completo de la solicitud se corta en 256 KB, y el base64 infla el archivo ~33%, así que el archivo efectivo máximo ronda los 190 KB. Para archivos más grandes, envía un enlace como mensaje de texto con POST /api/v1/messages.

Acepta Idempotency-Key igual que el envío de texto. El mensaje se atribuye según el modo de la clave (API REST o agente humano), con las mismas reglas.

Respuesta

{
  "data": {
    "id": "cm1msg...",
    "conversationId": "cm1conv...",
    "senderType": "SYSTEM",
    "contentType": "IMAGE",
    "mediaUrl": "https://...",
    "status": "SENT",
    "externalId": "wamid.abc...",
    "createdAt": "2026-03-23T10:30:00.000Z"
  }
}

Ejemplo cURL

curl -X POST \
  -H "Authorization: Bearer ak_tu_clave" \
  -H "Content-Type: application/json" \
  -d "{
    \"conversationId\": \"cm1conv...\",
    \"mediaType\": \"image\",
    \"mimeType\": \"image/jpeg\",
    \"filename\": \"recibo.jpg\",
    \"caption\": \"Tu comprobante de pago\",
    \"data\": \"$(base64 -w0 recibo.jpg)\"
  }" \
  https://tu-dominio.com/api/v1/messages/media

Errores comunes

HTTPCódigoCausa probableSolución
401UNAUTHORIZEDFalta el header AuthorizationAgrega Authorization: Bearer ak_...
401UNAUTHORIZEDClave inválida o revocadaVerifica la clave en Configuración → API
403FORBIDDENScope insuficienteCrea una clave nueva con el scope necesario
403FORBIDDENPlan sin acceso a APIActualiza tu plan a uno que incluya API dedicada
404NOT_FOUNDRecurso no encontrado o no pertenece a tu orgVerifica el ID del recurso
409CONFLICTDuplicado (ej. mismo teléfono)Busca el contacto existente antes de crear
401KEY_EXPIREDLa clave tenía fecha de vencimiento y ya pasóGenera una nueva desde Configuración → API
403ORIGIN_NOT_ALLOWEDLa clave tiene orígenes permitidos y el Origin no está en la listaAgrega el dominio, o usa *.dominio.com para subdominios
413PAYLOAD_TOO_LARGEEl cuerpo supera los 256 KB (el base64 de media cuenta)Archivos de hasta ~190 KB por /api/v1/messages/media; más grandes, como enlace en un mensaje de texto
422IDEMPOTENCY_KEY_REUSEDMisma Idempotency-Key con un cuerpo distintoUsa una clave nueva por cada operación distinta
429RATE_LIMITEDDemasiadas solicitudesEspera lo que indica Retry-After. Ver Límites.
424PROVIDER_ERRORMeta rechazó el envío (token vencido, número inválido…)El motivo viene en error.message. El mensaje queda en FAILED.

Ejemplos de integración

Flujo completo: enviar un mensaje a un contacto

Este ejemplo muestra cómo obtener los canales, buscar un contacto y enviarle un mensaje usando JavaScript (Node.js / fetch):

const API_URL = "https://tu-dominio.com";
const API_KEY = "ak_tu_clave_aqui";

const headers = {
  "Authorization": `Bearer ${API_KEY}`,
  "Content-Type": "application/json",
};

// 1. Obtener canales disponibles
const channelsRes = await fetch(`${API_URL}/api/v1/channels`, { headers });
const { data: channels } = await channelsRes.json();
const whatsappChannel = channels.find(ch => ch.type === "WHATSAPP");

console.log("Canal WhatsApp:", whatsappChannel.id);

// 2. Buscar contacto por teléfono
const contactsRes = await fetch(
  `${API_URL}/api/v1/contacts?search=584121234567`,
  { headers }
);
const { data: contacts } = await contactsRes.json();
const contact = contacts[0];

console.log("Contacto:", contact.id, contact.name);

// 3. Enviar mensaje
const msgRes = await fetch(`${API_URL}/api/v1/messages`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    contactId: contact.id,
    channelId: whatsappChannel.id,
    content: "Hola, tu pedido #1234 está listo para retiro.",
  }),
});

const { data: message } = await msgRes.json();
console.log("Mensaje enviado:", message.id, "Estado:", message.status);

Mismo flujo con cURL

# 1. Listar canales
curl -s -H "Authorization: Bearer ak_tu_clave" \
  https://tu-dominio.com/api/v1/channels

# 2. Buscar contacto
curl -s -H "Authorization: Bearer ak_tu_clave" \
  "https://tu-dominio.com/api/v1/contacts?search=584121234567"

# 3. Enviar mensaje
curl -X POST \
  -H "Authorization: Bearer ak_tu_clave" \
  -H "Content-Type: application/json" \
  -d '{
    "contactId": "CONTACT_ID",
    "channelId": "CHANNEL_ID",
    "content": "Hola, tu pedido #1234 está listo para retiro."
  }' \
  https://tu-dominio.com/api/v1/messages

Consejo: Guarda los IDs de canales y contactos frecuentes en tu base de datos para no tener que consultarlos en cada envío.

AvanzadoPlugins e IntegracionesAvanzadoServidor MCP
© 2026 Artificialic, Inc.
Estado de serviciosTérminosPrivacidad