Forma de la respuesta
{ "message_uuid": "...", "wamid": "..." }
{
"code": "WINDOW_CLOSED",
"message": "The 24-hour messaging window is closed. Send an approved template to reopen it."
}
{ code, message } — ambos campos están garantizados.
Haz
switch sobre code: es estable y forma parte del contrato. El
message es texto humano de ayuda y puede afinarse entre versiones de la
API sin aviso. Los mensajes se entregan en inglés (estándar de industria,
mismo patrón que Stripe, GitHub o Linear). Si necesitas mostrarlos
traducidos al usuario final, mapéalos en tu cliente por code.Familias de status
| HTTP | Significado | ¿Reintentar? |
|---|---|---|
2xx | Éxito — 200 lectura, 201 creación, 202 aceptado (async), 207 multi-status. | — |
4xx | Error del request — auth, validación o recurso inexistente. | No, corrígelo primero. |
429 | Rate limit excedido. | Sí, tras el Retry-After. |
5xx | Error del servidor. | Sí, con backoff. |
Catálogo de códigos
El status HTTP de cada código es típico: el endpoint puntual puede devolver
otro status si el contexto lo justifica. El authoritative por endpoint vive en
su especificación OpenAPI. La tabla aquí es referencia general, no
contrato vinculante por endpoint.Los códigos de ambigüedad de resolución de conversación (
PHONE_NUMBER_AMBIGUOUS,
USERNAME_AMBIGUOUS) pueden aparecer en
cualquier endpoint que acepte una referencia conversation por
phone/username/whatsapp_user_id, aunque su especificación liste otros
códigos 409.Auth y tenant
| Code | HTTP | Descripción |
|---|---|---|
INVALID_API_TOKEN | 401 | El token no existe o fue revocado. Genera uno nuevo en Settings → API Token. |
TOKEN_BUSINESS_MISMATCH | 403 | El token no pertenece al slug del negocio en la URL. Verifica que ambos refieran al mismo negocio. |
Rate limit
| Code | HTTP | Descripción |
|---|---|---|
RATE_LIMIT_EXCEEDED | 429 | Superaste el bucket de 60 req/min. Espera los segundos del header Retry-After. Ver Rate limits. |
Validación
| Code | HTTP | Descripción |
|---|---|---|
INVALID_REQUEST | 400 | El body no pasó la validación de schema (campo faltante, tipo incorrecto, valor fuera de rango). |
INVALID_CURSOR | 400 | El cursor de paginación es inválido o expiró. Reinicia el listado sin cursor. |
BATCH_LIMIT_EXCEEDED | 400 | El grupo de acciones excede el máximo permitido en una sola request. El message lleva el cap real. |
Conversación, contacto y canal
| Code | HTTP | Descripción |
|---|---|---|
CONVERSATION_NOT_FOUND | 404 | El identificador de conversation (uuid, whatsapp_user_id, username o phone) no resuelve a ninguna conversación del negocio. |
CONTACT_NOT_FOUND | 404 | El identificador de contacto no resuelve a ninguno del negocio. |
PHONE_NUMBER_AMBIGUOUS | 409 | Dos contactos distintos comparten ese teléfono en el canal indicado (números reciclados / identidades separadas). Identifica la conversación por whatsapp_user_id o uuid. El mismo número en canales distintos no es ambiguo: channel lo resuelve. |
USERNAME_AMBIGUOUS | 409 | Dos contactos distintos comparten ese username en el canal indicado. Identifica la conversación por whatsapp_user_id o uuid. El mismo username en canales distintos no es ambiguo: channel lo resuelve. |
CHANNEL_NOT_FOUND | 404 | La channel (clave pública del canal) enviada en la referencia no resuelve a ningún canal del negocio. Es obligatoria en todo request; cópiala en Configuración → Canales o lista tus canales con GET /channels. |
CHANNEL_NOT_CONFIGURED | 422 | La conversación no tiene un canal de WhatsApp configurado. Conecta uno en el dashboard antes de enviar. |
TESTER_CONVERSATION_NOT_WRITABLE | 422 | Las conversaciones tester no aceptan media real: send_message con archivo (file_uuid) se rechaza. Texto, respuestas rápidas y plantillas sí funcionan (simulados). |
TRANSCRIPTION_REQUIRED_TO_RESOLVE | 422 | La acción mark_resolved no puede cerrar la conversación: tiene un AI employee asignado y media entrante con transcripción pendiente. El AI arma su prompt desde el texto transcrito, así que sin él quedaría ciego a lo que envió el cliente. Completa la transcripción manual desde el dashboard y reintenta. Las conversaciones atendidas por un humano no disparan este error. |
TRANSCRIPTION_REQUIRED | 422 | La acción send_message lleva media (file_uuid) que la visión no puede transcribir (tipo no soportado o mayor a 20 MB) y no incluye el campo transcription. Provee la transcripción del archivo en la acción y reintenta. Es un error por-acción: se evalúa durante la ejecución en background. |
Tags
| Code | HTTP | Descripción |
|---|---|---|
TAG_NOT_FOUND | 404 | El tag no existe para este negocio. |
TAG_ALREADY_ASSIGNED | 409 | El tag ya está asignado a esta conversación. |
TAG_NOT_ASSIGNED | 404 | El tag no está asignado, por lo que no se puede remover. |
TAG_AMBIGUOUS | 409 | Hay varios tags con ese nombre. Usa tag_uuid para desambiguar. |
TAG_BUSINESS_MISMATCH | 400 | El tag pertenece a otro negocio. |
CANNOT_ASSIGN_DEFAULT_TAG | 422 | El tag es de sistema (lo administra el backend, ej. el de conversión pendiente) y no puede asignarse por API. |
CANNOT_REMOVE_DEFAULT_TAG | 422 | El tag es de sistema (lo administra el backend, ej. el de conversión pendiente) y no puede removerse por API. |
Mailboxes y categorías
| Code | HTTP | Descripción |
|---|---|---|
CATEGORY_NOT_FOUND | 404 | La categoría de mailbox no existe para este negocio. |
CATEGORY_BUSINESS_MISMATCH | 403 | La categoría pertenece a otro negocio. |
AMBIGUOUS_CATEGORY | 409 | Hay varias categorías con ese nombre. Usa category: { uuid } para desambiguar. |
MAILBOX_NOT_FOUND | 404 | El mailbox no existe para este negocio. |
MAILBOX_ALREADY_ASSIGNED | 409 | La conversación ya está en ese mailbox. |
AMBIGUOUS_MAILBOX | 409 | Hay varios mailboxes con ese nombre. Usa mailbox: { uuid } para desambiguar. |
NO_MAILBOXES_IN_CATEGORY | 422 | La categoría no tiene mailboxes configurados. Agrega al menos uno en el dashboard. |
Guards de precondición
| Code | HTTP | Descripción |
|---|---|---|
WINDOW_CLOSED | 422 | La ventana de 24h está cerrada. Envía una plantilla aprobada para reabrirla. |
WALLET_BLOCKED | 402 | El wallet de tokens del modelo AI requerido está vacío o bloqueado. Recarga para continuar. |
VISION_WALLET_BLOCKED | 402 | El wallet de vision tokens está vacío. El procesamiento de visión requiere recarga. |
VISION_BLOCKED | 402 | El hilo está bloqueado por visión (tokens de visión agotados o una recarga de tokens fallida), o su media entrante quedó sin transcripción después de que la visión terminó. Recarga tokens, o agrega la transcripción de la media desde el dashboard, antes de reintentar. |
NO_AI_EMPLOYEE_ASSIGNED | 422 | La conversación no tiene un empleado AI asignado. Asigna uno antes de ejecutar la acción. |
Schedules (mensajes programados)
| Code | HTTP | Descripción |
|---|---|---|
OWNER_MISMATCH | 422 | El executor del scheduled message no coincide con el owner actual de la conversación. Reasigna o cancela el schedule primero. |
SCHEDULE_CONFLICT | 409 | El schedule no se pudo guardar por un cambio concurrente o una request duplicada en la conversación: otra request está creando, reemplazando o cancelando un schedule al mismo tiempo, o esta request reusa un scheduled_uuid que ya existe. Si había una programación previa en la conversación, esta misma request ya la canceló, así que un GET refleja el estado real (normalmente sin schedule pending). Para reintentar, omite el scheduled_uuid (el servidor genera uno) o usa uno nuevo: reusar el scheduled_uuid que provocó el conflicto devuelve un 409 permanente. |
Empleados AI y ejecución
| Code | HTTP | Descripción |
|---|---|---|
AI_EMPLOYEE_NOT_FOUND | 404 | El empleado AI no existe para este negocio. |
AMBIGUOUS_AI_EMPLOYEE | 409 | Hay varios empleados con ese nombre. Usa ai_employee: { uuid } para desambiguar. |
AI_EMPLOYEE_ALREADY_ASSIGNED | 409 | Ese empleado AI ya está asignado a la conversación. |
INVALID_ASSIGNEE_FOR_RUN_AI | 409 | La conversación es human-owned y no se pasó ai_employee. Especifica uno o reasigna un AI primero. |
AI_EXECUTION_FAILED | 500 | La ejecución del AI falló inesperadamente. Reintenta; si persiste, revisa el dashboard del empleado. |
ACTIONS_DEPTH_EXCEEDED | 422 | Las acciones AI encadenadas exceden la profundidad máxima permitida en una request. |
Scheduled message action
| Code | HTTP | Descripción |
|---|---|---|
SCHEDULED_MESSAGE_ACTION_REQUIRED | 400 | Hay un scheduled message activo en la conversación. Especifica scheduled_message_action (reassign o cancel). |
SCHEDULED_MESSAGE_ACTION_INVALID | 400 | El valor de scheduled_message_action es inválido. Debe ser reassign o cancel. |
Uploads
| Code | HTTP | Descripción |
|---|---|---|
INVALID_FILE_TYPE | 400 | El tipo de archivo no está permitido. Ver la documentación para la lista de MIME types aceptados. |
FILE_TOO_LARGE | 400 | El archivo excede el tamaño máximo de su categoría (imagen 5 MB, video/audio 16 MB, documento 100 MB; text/plain tiene un tope propio de 5 MB). |
STORAGE_QUOTA_EXCEEDED | 413 | El negocio superó su cuota de storage, o tiene el storage deshabilitado (quota_bytes = 0). Libera espacio o sube de plan; si el storage está deshabilitado, contacta a soporte. |
STORAGE_QUOTA_CLEANUP_INSUFFICIENT | 507 | El storage está lleno y la limpieza automática no pudo liberar suficiente espacio para este upload. Libera espacio o sube de plan. |
FILE_NOT_FOUND | 404 | El file_uuid no resuelve para la operación pedida: no existe, no es de este negocio, o no está en el estado que ese endpoint requiere (en /files/confirm el archivo debe seguir pending — uno ya confirmado o rechazado también da 404; en la descarga de comprobantes debe ser un comprobante confirmado del paymentUuid pedido). El detalle por endpoint vive en su especificación OpenAPI. |
FILE_NOT_UPLOADED | 404 | El binario no llegó a Storage, o un blob vacío/parcial ocupa el path. Pide un nuevo /request-upload y reintenta el PUT — un re-PUT al mismo upload_url falla con Duplicate si el path ya está ocupado. |
MIME_MISMATCH | 400 | El MIME type real no coincide con el declarado. Re-sube con el Content-Type correcto. |
UPLOAD_FAILED | 500 | El upload no se pudo completar por un error de storage. Reintenta. |
TOO_MANY_PENDING_UPLOADS | 429 | El negocio tiene demasiados uploads sin confirmar. Confirma o deja expirar tus uploads pendientes antes de pedir más. Reintenta tras los segundos del header Retry-After. |
Mensajes
| Code | HTTP | Descripción |
|---|---|---|
PREPARE_FAILED | 500 | El mensaje no se pudo preparar (error de DB o upstream). Reintenta. |
SEND_FAILED | 502 | El envío a WhatsApp falló. Verifica la salud del canal y reintenta. |
BSUID_SEND_DISABLED | 409 | El contacto no tiene teléfono y el envío por identidad BSUID (business-scoped user ID) no está habilitado en la plataforma. No es una configuración por cuenta: no hay nada que habilitar de tu lado. |
PAYMENT_MARKER_UNKNOWN | 422 | El texto contiene un marcador de pago que no existe en el catálogo (típicamente un error de escritura). El mensaje se bloquea entero: corrige el nombre del marcador y reenvía. |
PAYMENT_MARKER_DATA_UNAVAILABLE | 422 | La conversación no tiene el dato que pide el marcador — por ejemplo, un perfil solo con link de pago no tiene CLABE. Repara los datos de pago de la conversación antes de reenviar. |
PAYMENT_MARKER_UNSUPPORTED_FIELD | 422 | El marcador está en un campo que no admite sustitución: el encabezado, el pie o el título de un botón de un mensaje interactivo, o el encabezado de texto / los parámetros de botón de una plantilla. En esos campos el largo se valida sobre el texto que envías —midiendo el marcador, no el dato—, así que sustituir podría desbordar el límite de Meta al entregar. Los marcadores solo funcionan en el cuerpo del mensaje o en las variables del cuerpo de una plantilla. |
PAYMENT_MARKER_NOT_ALLOWED_IN_TEMPLATE | 400 | Reservado. Las variables del cuerpo de una plantilla sí sustituyen marcadores desde la v0.3.0; lo que no los admite es el encabezado ni los botones, y eso devuelve PAYMENT_MARKER_UNSUPPORTED_FIELD. Ningún endpoint emite este código hoy. |
TEMPLATE_BODY_TOO_LONG | 422 | El cuerpo de la plantilla, ya con las variables interpoladas, supera los 1024 caracteres de Meta. Cualquier variable larga puede desbordarlo; el caso típico es un marcador sustituido por un dato más largo que él mismo (una CLABE mide 18 caracteres y ==clabe_spei== mide 14). Acorta el texto de la plantilla o el valor de la variable. |
PAYMENT_PROFILE_GENERATION_FAILED | 500 | No había datos de pago para esta conversación y generarlos falló. Reintenta en unos momentos. |
Respuestas rápidas y plantillas
| Code | HTTP | Descripción |
|---|---|---|
QUICK_REPLY_NOT_FOUND | 404 | La respuesta rápida no existe para este negocio. |
QUICK_REPLY_AMBIGUOUS | 409 | Hay varias respuestas rápidas con ese nombre. Usa quick_reply: { uuid } para desambiguar. |
QUICK_REPLY_EMPTY | 422 | La respuesta rápida no tiene acciones configuradas. Agrega al menos una en el dashboard. |
TEMPLATE_NOT_FOUND | 404 | La plantilla no existe para este negocio. |
TEMPLATE_NOT_APPROVED | 422 | La plantilla no está aprobada por Meta. La aprobación es requisito para enviar. |
AUTH_TEMPLATE_REQUIRES_PHONE | 409 | No se puede enviar una plantilla de autenticación a un contacto sin teléfono: Meta exige el número. Envía a un contacto con teléfono. |
TEMPLATE_AMBIGUOUS | 409 | El mismo nombre y language existe en 2 o más canales de WhatsApp conectados. El request no permite elegir a qué canal pertenece la plantilla, así que la ambigüedad no se puede resolver desde la integración. Contacta a soporte para resolver la colisión. |
TEMPLATE_VARIABLES_INVALID | 422 | Las variables del body no coinciden con la plantilla (conteo incorrecto, índice faltante o valor vacío). |
TEMPLATE_HEADER_MISMATCH | 400 | El tipo de header no coincide con el que declara la plantilla (ej: la plantilla espera imagen y enviaste texto). |
TEMPLATE_HEADER_TRANSCRIPTION_MISSING | 422 | El header media de la plantilla (en send_message con template, o en la deprecada send_quick_reply_or_template) no puede transcribirse por la visión (tipo no soportado o mayor a 20 MB) y falta el campo header.transcription. Provee la transcripción del header y reintenta. Es un error por-acción: se evalúa durante la ejecución en background. |
TEMPLATE_FAKE_REQUIRES_TESTER_CONVERSATION | 422 | La plantilla es del módulo Tester (fake) y solo puede enviarse a conversaciones tester. |
Pagos
| Code | HTTP | Descripción |
|---|---|---|
PAYMENT_NOT_FOUND | 404 | No existe un pago con ese paymentUuid para este negocio. |
Operaciones genéricas
| Code | HTTP | Descripción |
|---|---|---|
FETCH_FAILED | 500 | No se pudieron leer los datos necesarios para completar el request. Reintenta. |
UPDATE_FAILED | 500 | El cambio no se pudo aplicar (error de DB). Reintenta. |
UNKNOWN_ERROR | 500 | Error inesperado. Contacta a soporte si persiste. |