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.

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

ScopeDescripciónEndpoints
channels:readLeer canales conectadosGET /api/v1/channels
contacts:readListar y ver contactosGET /api/v1/contacts, GET /api/v1/contacts/:id
contacts:writeCrear, editar y eliminar contactosPOST, PATCH, DELETE /api/v1/contacts
messages:sendEnviar mensajes de textoPOST /api/v1/messages

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

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
idstringID 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
idstringID 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:write

Path params

ParámetroTipoRequeridoDescripción
idstringID 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.

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. Usa el primer canal conectado de ese tipo.

Contenido del mensaje (exactamente uno requerido)

ParámetroTipoRequeridoDescripción
contentstringNoTexto plano del mensaje
templateobjectNoPlantilla a enviar (ver estructura abajo)
template.idstringID de la plantilla
template.typestring"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.

Flujo interno

  1. Se resuelve el canal (por ID o primer canal conectado del tipo indicado).
  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

{
  "data": {
    "id": "cm1msg...",
    "conversationId": "cm1conv...",
    "senderType": "AGENT",
    "senderId": "cm1user...",
    "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": {
      "id": "cm1user...",
      "name": "Admin",
      "email": "[email protected]",
      "avatarUrl": null
    }
  }
}

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
404NOT_FOUNDCanal, contacto o plantilla no encontrada
400BAD_REQUESTCanal desconectado

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

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

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.