Errores y límites

Cómo interpretar las respuestas de error de la API, qué significan los códigos por dominio y cómo manejar rate limits y reintentos.

Anatomía de un error

Todas las respuestas de error comparten la misma estructura. El campo code es un identificador estable con formato {dominio}_{número} que puedes usar para manejar cada caso de forma programática (no dependas del texto de message, que es legible pero puede cambiar).

{
  "success": false,
  "message": "API Key inválida o no existe.",
  "status": 401,
  "code": "auth_05"
}
CampoDescripción
successfalse en todo error.
messageDescripción legible en español.
statusCampo legado dentro del cuerpo. No dependas de él: conserva su valor histórico y no coincide con el status HTTP real de la respuesta (en casos reclasificados como auth_06 la diferencia es explícita). Usa el status HTTP de transporte y el code.
codeCódigo estructurado {dominio}_{número}.
request_idUUID de seguimiento (presente en errores 500). Inclúyelo al contactar a soporte.

Guarda el request_id junto con el log de la operación, pero no lo reutilices como identificador propio de mensajes, campañas o transacciones: para eso usa el reference que devuelve cada envío. Los eventos de webhooks son independientes y no incluyen el request_id de la petición original.

Códigos HTTP

HTTPSignificado¿Reintentar?
200Éxito.No aplica
400Error de validación o de lógica de negocio (parámetros, mensaje, contenido rechazado).No, corrige la petición.
401API Key ausente o inválida.No, revisa tu llave.
402Saldo insuficiente para completar la operación (code: sms_07).No, recarga créditos.
403Sin permiso: IP no autorizada, cuenta deshabilitada o cuenta administradora inválida.No, revisa el acceso de tu cuenta.
410Recurso o método deprecado (p.ej. el header heredado token).No, migra al método vigente.
429Se excedió el rate limit.Sí, con backoff.
500Error interno del servidor.Sí, con backoff; guarda el request_id.
502Falla de un proveedor o canal aguas abajo al entregar el mensaje.Sí, con backoff.

El code sigue siendo la forma más precisa de identificar un error: el status HTTP es la categoría y varios code distintos comparten el mismo status. Verifica siempre success: false + el code, no solo el status.

Status HTTP semántico (comportamiento por defecto). La API responde con el status HTTP real por caso (400, 401, 402, 403, 404, 409, 410, 429, 500, 502). El cuerpo no cambia (mismos success, code, message); solo cambia la línea de status HTTP. Aplica a los códigos de autenticación (auth_*), de envío de SMS (sms_*), de WhatsApp (whatsapp_*), de voz (/voice/send), de reportes (/reports/*, /campaigns), de gestión de subcuentas (/manager/subaccount/*), de integraciones (webhooks del cliente /webhook/* y hooks de Zapier /zapier/hooks/*) y de la verificación OTP v1 en formato JSON (/protected/json/phones/verification/*, deprecada — ver abajo).

Opt-out temporal (comportamiento heredado). Si tu integración todavía asume que toda respuesta llega con HTTP 200 y el error solo en el cuerpo, envía el header X-Api-Http-Semantics: 0 en tus peticiones: la API responderá 200 en todos los casos, como antes. Es un puente temporal mientras migras — se retirará en una versión futura. Los endpoints /v1/* y las variantes XML de OTP (/protected/xml/*) conservan el 200 en sus propios errores, pero los errores de autenticación (auth_*: llave ausente, inválida, cuenta deshabilitada, header token deprecado) sí llegan con su status real (401/403/410) también en esas rutas, porque se resuelven antes de entrar al endpoint.

Los endpoints OTP v2 (/v2/otp*) responden con status HTTP semántico siempre (sin necesidad del header) y usan un envelope propio con slug en error (no code), más hint y next. Detalle abajo en la sección Verificación OTP.

Códigos por dominio

El prefijo del code indica el área de la API donde se originó la respuesta y el número identifica el caso exacto. Esta es la referencia de los códigos que la API documenta hoy; el message siempre acompaña al code con el detalle en español.

No todos los códigos son errores. Los que aparecen con HTTP 200 (por ejemplo sms_11) indican éxito y llegan con success: true. Por eso revisa siempre success antes que el code.

Autenticación y acceso (auth_)

CódigoCategoríaSignificado
auth_01401No se proporcionó API Key.
auth_03401API Key inválida en endpoints de autorización (p.ej. /zapier/auth).
auth_04403La IP no está en la lista blanca de tu cuenta.
auth_05401API Key inválida o inexistente.
auth_06403Cuenta deshabilitada o suspendida.
auth_07403Cuenta administradora inválida o deshabilitada.
auth_99500Error interno al procesar la autenticación; reintenta con backoff.
auth_deprecated410Usaste el encabezado heredado token; migra al encabezado apikey.

Verificación OTP (otp_)

Los endpoints OTP v2 (/v2/otp, /v2/otp/verify, /v2/otp/status) usan un envelope distinto al resto de la API: el slug estable va en error, el status HTTP es real (la columna HTTP de abajo es el código de transporte que efectivamente recibes) y cada error trae hint (qué hacer, en español) y next (endpoints sugeridos).

{
  "success": false,
  "state": "pending",
  "error": "otp_incorrect_code",
  "message": "El código es incorrecto.",
  "hint": "Código incorrecto. Te quedan 3 intentos.",
  "next": ["/v2/otp/verify", "/v2/otp"],
  "attempts_remaining": 3,
  "request_id": "550e8400-e29b-41d4-a716-446655440000"
}
errorHTTPSignificado
otp_missing_param400Falta un parámetro obligatorio (hint indica cuál).
otp_invalid_param400Parámetro inválido o que no aplica al endpoint (incluye parámetros de la API OTP anterior).
otp_invalid_channel400channel debe ser sms, whatsapp o voice.
otp_message_invalid400El message no incluye {{code}}/{{company}}, usa caracteres fuera de GSM-7 o excede 160 caracteres.
otp_incorrect_code401Código incorrecto (la respuesta trae attempts_remaining).
otp_insufficient_credits402Saldo insuficiente (incluye code: sms_07, el código estándar de saldo de toda la API).
otp_not_found404No hay verificación activa para el número.
otp_already_verified409El número ya está verificado.
otp_code_expired410El código expiró.
otp_throttled429Cooldown de reenvío (30s) activo; espera resend_available_in segundos (también en el header Retry-After).
otp_max_attempts429Se agotaron los 7 intentos de la ronda; rota el código con code_rotate: true.
otp_internal500Error inesperado; reintenta con backoff y guarda el request_id.
otp_send_failed502El canal no pudo entregar el mensaje.

OTP v1 (deprecado). Los endpoints heredados /protected/json/phones/verification/{start,check,resend,reset} usan códigos verification_* (flujos start/resend/reset) y validation_* (flujo check), no el envelope de v2. Responden status HTTP semántico alineado con v2: código expirado→410, ya verificado→409, no encontrado (sin verificación activa)→404, código incorrecto (check)→401, cooldown de reenvío / intentos agotados→429, validación de parámetros→400, error interno→500. Excepción conocida: el saldo insuficiente sale como 500 (no 402), porque en v1 el code sms_07 no se propaga. Las variantes XML (/protected/xml/*) conservan el 200 en sus propios errores; los de autenticación (auth_*) sí llegan con su status real (401/403/410), porque se resuelven antes de entrar al endpoint. Migra a OTP v2 (/v2/otp*), que es semántico por defecto y expone estos estados de forma explícita.

Envío de mensajes (sms_, whatsapp_)

CódigoCategoríaSignificado
sms_01400Mensaje no definido.
sms_02400Mensaje demasiado largo.
sms_03400Números no definidos.
sms_04400Número con formato incorrecto (deben ser 10 dígitos).
sms_05400Código de país no definido.
sms_06400Nombre de campaña de más de 100 caracteres.
sms_07402Créditos insuficientes para completar el envío.
sms_09500Error interno al crear la campaña.
sms_11200Mensajes enviados correctamente.
sms_12500Error interno inesperado al procesar la petición (incluye referencia ERR:xxx).
sms_15400Más de 500 destinatarios en una petición.
sms_16400Número repetido en la lista de destinatarios.
sms_17400Límite diario de sandbox excedido (1000 envíos de prueba).
sms_18400País no soportado.
sms_19200Envío exitoso a un número fijo (informativo).
sms_20400La fecha programada ya pasó.
sms_21400Formato de fecha inválido.
sms_22500Error interno al gestionar la campaña (incluye referencia ERR:xxx).
sms_23502El envío de los mensajes falló en un canal o proveedor aguas abajo.
sms_24400Lada no soportada.
sms_29 / sms_31400Acortador: no se encontró URL en el mensaje / hay más de una URL.
sms_32 / sms_33500Error interno al acortar la URL del mensaje.
sms_34400El mensaje no pudo procesarse: su contenido fue rechazado. Corrige el contenido antes de reintentar.
sms_36400channel inválido (solo 1 = SMS o 2 = RCS).
sms_37 / sms_38400RCS: remitente obligatorio / remitente no vinculado a la cuenta.
sms_39 / sms_40400flash inválido (solo 0 o 1) / flash no compatible con RCS.
sms_41502El envío de los mensajes falló en un canal o proveedor aguas abajo.
whatsapp_04400Instancia (instance_id) no definida.
whatsapp_05400type inválido (usa text, image, video, audio o document).
whatsapp_06400Mensaje no definido.
whatsapp_07400Número de destino no definido.
whatsapp_08404La instancia no existe o está inactiva.
whatsapp_20502La línea de WhatsApp no está disponible; reintenta más tarde.
whatsapp_21200Mensaje de WhatsApp enviado o programado.
whatsapp_22400Código de país no definido o inválido.
whatsapp_23400Número con formato incorrecto.
whatsapp_24400La fecha de envío programada es anterior a la actual.
whatsapp_28 / whatsapp_38400Mensaje de más de 1000 caracteres / caption de más de 1000 caracteres.
whatsapp_29400Formato de fecha inválido.
whatsapp_30 / whatsapp_31 / whatsapp_32500Error interno al resolver el servidor / guardar la confirmación de lectura / obtener las URLs de WhatsApp.
whatsapp_35400País no soportado.
whatsapp_36500Error interno al generar el mensaje.
whatsapp_37400URL multimedia inválida o con extensión no permitida.

WhatsApp también puede devolver sms_07 (402, saldo insuficiente), sms_12 (500, error interno de créditos) y err_03 (500, error interno inesperado).

Throttle anti-bulk de /whatsapp/send. Este endpoint no usa el rate limit global de 100 mensajes/segundo: en su lugar, permite máximo 1 envío cada 60 segundos; al excederlo la cuenta queda bloqueada 2 minutos. El cuerpo de este error es distinto al resto de la API (no lleva success ni code):

{
  "error": true,
  "message": "Too many requests",
  "rate-limited": "2 minutes",
  "reason": "Bulk messages using WhatsApp is not allowed, use SMS instead."
}

Para volumen, usa /sms/send en lugar de llamar /whatsapp/send en loop.

Voz (/voice/send) reutiliza el pipeline de envío de SMS, por lo que comparte sms_07 (402, saldo insuficiente), sms_23/sms_41 (502, envío fallido aguas abajo) y sms_12 (500, error interno). Sus validaciones propias (número, país, plantilla, código) usan error_02 (400) y sus errores internos error_03/err_03 (500).

Contactos y listas (contact_, agenda_)

CódigoCategoríaSignificado
contact_01400list_key no definido.
contact_02400Número no definido.
contact_03400Número con formato incorrecto (mínimo 10 dígitos, solo números).
contact_04 / contact_06400Nombre / email de más de 100 caracteres.
contact_05400Email con formato incorrecto.
contact_07404Lista no encontrada.
contact_09404Al duplicar: el contacto de origen no existe.
contact_10409Al duplicar: el contacto ya existe en la lista destino.
contact_11500No se pudo insertar el contacto (error interno).
contact_13200Contacto agregado.
contact_14500No se pudo actualizar el contacto (error interno).
contact_15200Contacto actualizado.
contact_16400Parámetro inválido o contacto no definido.
contact_17409La lista está vinculada a una herramienta y no admite alta/edición manual de contactos.
contact_19404 / 500El contacto no existe (404) o falló su eliminación (500).
agenda_01200Lista creada (devuelve list_key).
agenda_02400Nombre de lista no definido.
agenda_03409Ya existe una lista con ese nombre en la cuenta.
agenda_04 / agenda_05500No se pudo crear la lista (error interno).
agendas_01200Consulta de listas exitosa.
change_name_01200Lista renombrada.
change_name_02 / change_name_03400Falta list_key / falta el nuevo nombre.
change_name_04500No se pudo renombrar la lista (error interno).
get_contacts_01200Consulta de contactos exitosa.
get_contacts_02400list_key no definido.
delete_01200Lista eliminada.
err_01404 / 500La lista no existe (404) o falló la operación (500).
contactlist_02400query de más de 100 caracteres.
contactlist_03200Búsqueda de listas exitosa.
contactlist_04 / contactlist_05400limit / page no numéricos.
contactlist_06400Página solicitada fuera de rango.

En los endpoints de contactos y listas, los códigos genéricos err_01, err_03 y contact_19 tienen un status HTTP que depende del caso: 404 cuando el recurso (lista o contacto) no existe, y 500 cuando la operación falla internamente. Guíate por el status HTTP y el message.

Monedero (contact_add_, balance_, new_sale_, wallets_get_)

Los endpoints de monedero (/wallet/*) operan a través de un colaborador (usertool_id) con permiso sobre el monedero.

CódigoCategoríaSignificado
wallets_get_01200Consulta de monederos exitosa.
contact_add_01200Contacto de monedero agregado.
balance_update_01200Saldo del contacto actualizado.
balance_get_01200Consulta de saldo exitosa.
new_sale_01200Venta registrada.
contact_add_0206400Validación al agregar contacto (falta wallet_key/phone/customer_name/usertool_id o formato inválido).
wallet_manager_1921400Canal de notificación / email indefinido o con formato inválido.
balance_update_0208400Validación al actualizar saldo.
balance_get_02 / balance_get_03400Falta wallet_key / número con formato incorrecto.
new_sale_0209, new_sale_11400Validación al registrar venta (incluye falta de ticket).
contact_add_07 / balance_update_09 / balance_get_06 / new_sale_10404El monedero (wallet_key) no existe.
balance_update_10 / balance_get_04 / new_sale_12404El contacto no está registrado en el monedero.
loyalty_contact_add_25 / loyalty_contact_add_26403El colaborador (usertool_id) no tiene API Key o permiso sobre el monedero.
balance_update_11 / balance_update_12403El colaborador no tiene API Key o permiso sobre el monedero.
new_sale_13 / new_sale_14403El colaborador no tiene API Key o permiso sobre el monedero.
err_toolusers_01 / err_toolusers_02403El usertool_id no pertenece a la cuenta.
new_sale_15500Error interno al registrar la venta.
err_03500Error interno inesperado (catch genérico, en cualquier endpoint de monedero).

Solicitud de pago (paymentrequest_)

CódigoCategoríaSignificado
paymentrequest_01200Solicitud de pago generada o enviada.
paymentrequest_02400Error de validación (número, país, plantilla, mensaje, monto, nombre, email, custom o descripción).
paymentrequest_03404La plantilla (template) no existe o no está activa.
paymentrequest_06409La configuración de pasarela de la cuenta no es compatible con cobros de suscripción (requiere Stripe como única pasarela activa).
paymentrequest_00 / paymentrequest_04 / paymentrequest_05 / paymentrequest_07500Error interno (procesamiento, creación de agenda/contacto, envío del SMS o creación del producto en Stripe).

paymentrequest_05 se devuelve cuando no se pudo enviar el SMS de la solicitud. Si persiste, verifica tu saldo con /credits/consult.

Gestión de subcuentas (subaccount_, admin_credits_)

Endpoints administrativos /manager/subaccount/* (requieren cuenta administradora). Algunos code se reutilizan con distinto significado según el endpoint; por eso muestran dos categorías HTTP posibles.

CódigoCategoríaSignificado
subaccount_01200 / 404Éxito (alta o archivado de subcuenta) / en /get_apikey: no se encontró la API key de la subcuenta.
subaccount_02400 / 409Email con formato incorrecto / en /archive: la subcuenta ya está archivada.
subaccount_03400 / 500Email demasiado largo / en /archive: error interno al archivar la subcuenta.
subaccount_04400 / 404Email requerido / en /archive: la subcuenta no existe.
subaccount_05 a subaccount_10400Validación de password / nombre / número (corto, largo, requerido, formato).
subaccount_11409El email ya está registrado en otra cuenta.
subaccount_12409El número ya está registrado en otra cuenta.
subaccount_13 / subaccount_14500Error interno al crear la subcuenta / su configuración.
subaccount_15500Error interno al asignar la ruta de la subcuenta.
subaccount_credits_06 / subaccount_credits_09200Créditos transferidos / retirados de la subcuenta.
subaccount_credits_01 / _03 / _04 / _05 / _12400Validación de créditos (tipo o monto no definido, decimal, ≤0, tipo inválido).
subaccount_credits_10500Error interno al aplicar el movimiento de créditos.
subaccount_credits_11409Créditos insuficientes para completar la operación.
admin_credits_01404La subcuenta indicada no existe.

Lealtad (loyalty_)

Endpoints /loyalty/* (tarjetas de lealtad). Operan a través de un colaborador (usertool_id) con permiso sobre la herramienta.

CódigoCategoríaSignificado
loyalty_get_01200Listado de tarjetas obtenido.
loyalty_02404El loyalty_key (hash de tarjeta) es incorrecto o no existe.
loyalty_03403La tarjeta de lealtad está deshabilitada.
loyalty_contact_add_01200Contacto de lealtad creado.
loyalty_contact_add_02 a loyalty_contact_add_06400Validación: loyalty_key, número (formato), nombre o email faltante/incorrecto.
loyalty_contact_add_22 a loyalty_contact_add_24400Canal a notificar indefinido, o email indefinido/con formato inválido.
loyalty_contact_add_25 / loyalty_contact_add_26403El colaborador (usertool_id) no tiene API Key o permiso sobre la tarjeta.
loyalty_contact_add_07500Error interno al crear el contacto de lealtad.
loyalty_contact_get_02 / loyalty_contact_get_03400loyalty_key no definido / número con formato incorrecto.
loyalty_contact_get_04404La tarjeta de lealtad no existe.
loyalty_contact_get_05404El contacto no está registrado en la tarjeta.
loyalty_sale_01200Venta registrada.
loyalty_sale_03 a loyalty_sale_06, loyalty_sale_09, loyalty_sale_10400Validación: loyalty_key, número, ticket o monto faltante/incorrecto.
loyalty_sale_07404El contacto no existe en la tarjeta.
loyalty_sale_21 / loyalty_sale_22403El colaborador no tiene API Key o permiso sobre la tarjeta.

Colaboradores (toolusers_, err_toolusers_)

Endpoints /toolusers/get, /employee/add, /employee/disable (gestión de cajeros/gerentes).

CódigoCategoríaSignificado
tool_users_01 / toolusers_01200Listado obtenido / colaborador creado o archivado.
err_toolusers_02400Validación: tipo, nombre, sucursal, número o password faltante/inválido, o línea/número no soportado.
err_toolusers_03409Ya existe un colaborador con ese nombre o número.
err_toolusers_04500Error interno al crear/archivar el colaborador.
err_toolusers_01500Error interno inesperado al procesar la operación.

Otros dominios

CódigoCategoríaSignificado
credit_01200Consulta de créditos exitosa.
report_01 / report_02400Falta start_date / falta end_date.
report_03400start_date es posterior a end_date.
report_04200Reporte generado.
report_05429Límite de peticiones de reporte por cuenta excedido.
campaigns_02400(/campaigns) start_date es posterior a end_date.
details_02400campaign_id no definido o no numérico.
details_03404La campaña no existe.
details_04409La campaña aún se está procesando (aún no consultable).
webhook_01 / webhook_02200 / 500(/webhook/get) Configuración obtenida / error interno al leerla.
add_webhook_01200Webhook registrado.
add_webhook_02 a add_webhook_06400URL no definida, formato inválido, host no permitido, estado no definido o inválido, o la URL no respondió a la verificación.
add_webhook_07 / add_webhook_08500Error interno al guardar el webhook.
status_webhook_01200Estado del webhook actualizado.
status_webhook_02 / status_webhook_03400Estado no definido / inválido (solo 0 o 1).
status_webhook_04 / status_webhook_05500Error interno al actualizar el estado.
delete_webhook_01200Webhook eliminado.
delete_webhook_02 / delete_webhook_03500Error interno al eliminar el webhook.
err_03400/500Error genérico; el message describe la causa.
rate_01429Rate limit excedido.
server_01500Error interno del servidor.

¿Recibiste un code que no está en esta tabla? El message de la respuesta describe la causa. Cada endpoint también lista en la Referencia API los códigos que puede devolver, por ejemplo /sms/send o /v2/otp/verify.

Hooks de Zapier (/zapier/hooks/subscribe, /zapier/hooks/unsubscribe) — usados por la integración de Zapier — responden sin campo code (solo success + message). Al suscribir, si el hook ya existe → 409; al desuscribir, si no existe → 404; un fallo interno (BD) → 500. Con el opt-out X-Api-Http-Semantics: 0 responden 200 como el resto de la API.

Errores frecuentes de SMS

// Créditos insuficientes
{ "success": false, "message": "insufficient_credits", "code": "sms_07" }
 
// Mensaje excede el límite de caracteres
{ "success": false, "message": "message_too_long", "code": "sms_02" }

Antes de un envío grande, valida el saldo con /credits/consult para no fallar a mitad de la campaña con sms_07.

Rate limits

Entorno / endpointLímiteAl exceder
Producción (todos los endpoints excepto /whatsapp/send)100 mensajes/segundoHTTP 429, code: rate_01
Sandbox1000 envíos de prueba/díasuccess: false, code: sms_17
/whatsapp/send1 envío/60 segundos (no aplica el límite de 100 mensajes/segundo)HTTP 429, bloqueo de 2 minutos, cuerpo propio (error/rate-limited/reason, sin code)

Manejo de errores

1

Revisa success primero

Si success es false, no proceses la respuesta como éxito aunque el HTTP sea 200.

2

Ramifica por code, no por message

Usa el code estructurado (sms_07, auth_04, etc.) en tu lógica. El message es para logs y humanos.

3

Reintenta solo 429 y 500

Implementa reintentos con backoff exponencial únicamente en 429 y 500. Los 4xx restantes requieren corregir la petición.

4

Registra el request_id

En errores 500, guarda el request_id y compártelo con soporte para acelerar el diagnóstico.

Con esto puedes manejar cualquier respuesta de la API de forma robusta. Vuelve a la Guía rápida o explora la Referencia API.