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"
}
| Campo | Descripción |
|---|---|
success | false en todo error. |
message | Descripción legible en español. |
status | Campo 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. |
code | Código estructurado {dominio}_{número}. |
request_id | UUID 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
| HTTP | Significado | ¿Reintentar? |
|---|---|---|
200 | Éxito. | No aplica |
400 | Error de validación o de lógica de negocio (parámetros, mensaje, contenido rechazado). | No, corrige la petición. |
401 | API Key ausente o inválida. | No, revisa tu llave. |
402 | Saldo insuficiente para completar la operación (code: sms_07). | No, recarga créditos. |
403 | Sin permiso: IP no autorizada, cuenta deshabilitada o cuenta administradora inválida. | No, revisa el acceso de tu cuenta. |
410 | Recurso o método deprecado (p.ej. el header heredado token). | No, migra al método vigente. |
429 | Se excedió el rate limit. | Sí, con backoff. |
500 | Error interno del servidor. | Sí, con backoff; guarda el request_id. |
502 | Falla 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.
Cuenta deshabilitada (auth_06). Mientras tu cuenta esté deshabilitada, el status HTTP de auth_06 depende del header de cada petición: con X-Api-Http-Semantics: 0 llega con 200; con X-Api-Http-Semantics: 1, con 403. Sin el header, las peticiones de plataformas de automatización reciben 200 (ver Plataformas de automatización abajo), y el resto puede recibir 403 o 200. Si tu integración depende del status, envía el header de forma explícita; en cualquier caso, identifícalo por el code.
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_*, salvo auth_06: ver Cuenta deshabilitada arriba), 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 de la verificación OTP v1 en formato JSON (/protected/json/phones/verification/*, deprecada — ver abajo). Las rutas /zapier/* y las peticiones de algunas plataformas de automatización reciben por defecto el comportamiento heredado (ver Plataformas de automatización 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 — incluidos los errores de autenticación (auth_*: llave ausente, inválida, header token deprecado), que usan el mismo gate que el resto de la API. Es un puente temporal mientras migras — se retirará en una versión futura. Dos excepciones a "todos los casos": los endpoints /v1/* y las variantes XML de OTP (/protected/xml/*), cuyos propios errores nunca traen status HTTP semántico sin importar el header — salvo el saldo insuficiente (sms_07) de /v1/sms/send y /v1/sms/send/multi cuando tu saldo está en 0, que llega con 402 como en el resto de la API (y con 200 si vienes de una plataforma de automatización); si tienes saldo pero no alcanza para el lote, ese sms_07 llega con 200; y salvo el bloqueo por saldo en 0 de OTP v1, que alcanza también a las variantes XML (ver la nota de OTP v1 abajo); y OTP v2, cuyos propios errores ignoran este header y siempre responden semántico (ver nota abajo). Los errores de autenticación (auth_*) en /v2/otp* sí respetan el header, como en el resto de la API.
Plataformas de automatización. Como algunas plataformas no permiten agregar headers personalizados, la API les aplica este opt-out automáticamente, sin que envíes el header, en dos casos:
- Peticiones cuyo User-Agent identifica a Make (o Integromat) o a Zapier, en cualquier endpoint.
- Cualquier petición a las rutas
/zapier/*, que son las que usan Zapier, Pabbly Connect y GoHighLevel para sus envíos.
Fuera de esos dos casos la API no garantiza reconocer la plataforma: si tu integración necesita el 200, envía X-Api-Http-Semantics: 0. Las excepciones de arriba (/v1/*, variantes XML de OTP y errores propios de OTP v2) aplican igual, salvo el saldo insuficiente de OTP v2 (otp_insufficient_credits, con code: sms_07), que en estos casos llega con 200. Si desde una plataforma necesitas el status HTTP real, envía X-Api-Http-Semantics: 1.
Los endpoints OTP v2 (/v2/otp*) responden con status HTTP semántico siempre en sus propios errores (sin necesidad del header, y aunque envíes X-Api-Http-Semantics: 0; los errores de autenticación auth_* siguen el mismo gate que el resto de la API; el saldo insuficiente llega con 200 a las plataformas de automatización, ver Plataformas de automatización arriba) 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ódigo | Categoría | Significado |
|---|---|---|
auth_01 | 401 | No se proporcionó API Key. |
auth_03 | 401 | API Key inválida en endpoints de autorización (p.ej. /zapier/auth; como toda ruta /zapier/*, sin header llega con 200: ver Plataformas de automatización). |
auth_04 | 403 | La IP no está en la lista blanca de tu cuenta. |
auth_05 | 401 | API Key inválida o inexistente. |
auth_06 | 403 | Cuenta deshabilitada o suspendida. El status HTTP puede variar: ver Cuenta deshabilitada arriba. |
auth_07 | 403 | Cuenta administradora inválida o deshabilitada. |
auth_99 | 500 | Error interno al procesar la autenticación; reintenta con backoff. |
auth_deprecated | 410 | Usaste 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"
}
error | HTTP | Significado |
|---|---|---|
otp_missing_param | 400 | Falta un parámetro obligatorio (hint indica cuál). |
otp_invalid_param | 400 | Parámetro inválido o que no aplica al endpoint (incluye parámetros de la API OTP anterior). |
otp_invalid_channel | 400 | channel debe ser sms, whatsapp o voice. |
otp_message_invalid | 400 | El message no incluye {{code}}/{{company}}, usa caracteres fuera de GSM-7 o excede 160 caracteres. |
otp_incorrect_code | 401 | Código incorrecto (la respuesta trae attempts_remaining). |
otp_insufficient_credits | 402 | Saldo insuficiente (incluye code: sms_07, el código estándar de saldo de toda la API). Desde plataformas de automatización llega con 200: ver Plataformas de automatización. |
otp_not_found | 404 | No hay verificación activa para el número. |
otp_already_verified | 409 | El número ya está verificado. |
otp_code_expired | 410 | El código expiró. |
otp_throttled | 429 | Cooldown de reenvío (30s) activo; espera resend_available_in segundos (también en el header Retry-After). |
otp_max_attempts | 429 | Se agotaron los 7 intentos de la ronda; rota el código con code_rotate: true. |
otp_internal | 500 | Error inesperado; reintenta con backoff y guarda el request_id. |
otp_send_failed | 502 | El 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. Saldo insuficiente: lo originan start y resend (los que envían el código). En v1 el code sms_07 no se propaga, así que el cuerpo trae code: verification_05 con el mensaje «Créditos insuficientes. Actualmente cuentas con N créditos». Con tu saldo en 0 llega con 402, como en el resto de la API (y con 200 si envías X-Api-Http-Semantics: 0 o vienes de una plataforma de automatización); si tienes saldo pero no alcanza para el código, sale como 500. verification_05 también es el código de otros fallos de envío: distingue el saldo por el mensaje, no por el code. Una vez detectado el saldo en 0 (aquí o en cualquier envío de la cuenta), los siguientes requests de la cuenta a todos estos endpoints —incluidos check, reset y las variantes XML— pueden recibir ese mismo error de saldo con 402 (o 200 en los mismos casos) mientras la cuenta siga sin saldo; su cuerpo es el de la primera detección, así que puede traer code: sms_07 en vez de verification_05, y llega en JSON aun en las variantes XML. Las variantes XML (/protected/xml/*) conservan el 200 en sus propios errores sin importar el header. Los errores de autenticación (auth_*) en esas rutas siguen el mismo gate que el resto de la API: por defecto llegan con su status real (401/403/410; para auth_06, ver Cuenta deshabilitada), pero con el opt-out X-Api-Http-Semantics: 0 también vuelven a 200. 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ódigo | Categoría | Significado |
|---|---|---|
sms_01 | 400 | Mensaje no definido. |
sms_02 | 400 | Mensaje demasiado largo. |
sms_03 | 400 | Números no definidos. |
sms_04 | 400 | Número con formato incorrecto (deben ser 10 dígitos). |
sms_05 | 400 | Código de país no definido. |
sms_06 | 400 | Nombre de campaña de más de 100 caracteres. |
sms_07 | 402 | Créditos insuficientes para completar el envío. |
sms_09 | 500 | Error interno al crear la campaña. |
sms_11 | 200 | Mensajes enviados correctamente. |
sms_12 | 500 | Error interno inesperado al procesar la petición (incluye referencia ERR:xxx). |
sms_15 | 400 | Más de 500 destinatarios en una petición. |
sms_16 | 400 | Número repetido en la lista de destinatarios. |
sms_17 | 400 | Límite diario de sandbox excedido (100 envíos de prueba). |
sms_18 | 400 | País no soportado. |
sms_19 | 200 | Envío exitoso a un número fijo (informativo). |
sms_20 | 400 | La fecha programada ya pasó. |
sms_21 | 400 | Formato de fecha inválido. |
sms_22 | 500 | Error interno al gestionar la campaña (incluye referencia ERR:xxx). |
sms_23 | 502 | El envío de los mensajes falló en un canal o proveedor aguas abajo. |
sms_24 | 400 | Lada no soportada. |
sms_29 / sms_31 | 400 | Acortador: no se encontró URL en el mensaje / hay más de una URL. |
sms_32 / sms_33 | 500 | Error interno al acortar la URL del mensaje. |
sms_34 | 400 | El mensaje no pudo procesarse: su contenido fue rechazado. Corrige el contenido antes de reintentar. |
sms_36 | 400 | channel inválido (solo 1 = SMS o 2 = RCS). |
sms_37 / sms_38 | 400 | RCS: remitente obligatorio / remitente no vinculado a la cuenta. |
sms_39 / sms_40 | 400 | flash inválido (solo 0 o 1) / flash no compatible con RCS. |
sms_41 | 502 | El envío de los mensajes falló en un canal o proveedor aguas abajo. |
whatsapp_04 | 400 | Instancia (instance_id) no definida. |
whatsapp_05 | 400 | type inválido (usa text, image, video, audio o document). |
whatsapp_06 | 400 | Mensaje no definido. |
whatsapp_07 | 400 | Número de destino no definido. |
whatsapp_08 | 404 | La instancia no existe o está inactiva. |
whatsapp_20 | 502 | La línea de WhatsApp no está disponible; reintenta más tarde. |
whatsapp_21 | 200 | Mensaje de WhatsApp enviado o programado. |
whatsapp_22 | 400 | Código de país no definido o inválido. |
whatsapp_23 | 400 | Número con formato incorrecto. |
whatsapp_24 | 400 | La fecha de envío programada es anterior a la actual. |
whatsapp_28 / whatsapp_38 | 400 | Mensaje de más de 1000 caracteres / caption de más de 1000 caracteres. |
whatsapp_29 | 400 | Formato de fecha inválido. |
whatsapp_30 / whatsapp_31 / whatsapp_32 | 500 | Error interno al resolver el servidor / guardar la confirmación de lectura / obtener las URLs de WhatsApp. |
whatsapp_35 | 400 | País no soportado. |
whatsapp_36 | 500 | Error interno al generar el mensaje. |
whatsapp_37 | 400 | URL 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ódigo | Categoría | Significado |
|---|---|---|
contact_01 | 400 | list_key no definido. |
contact_02 | 400 | Número no definido. |
contact_03 | 400 | Número con formato incorrecto (mínimo 10 dígitos, solo números). |
contact_04 / contact_06 | 400 | Nombre / email de más de 100 caracteres. |
contact_05 | 400 | Email con formato incorrecto. |
contact_07 | 404 | Lista no encontrada. |
contact_09 | 404 | Al duplicar: el contacto de origen no existe. |
contact_10 | 409 | Al duplicar: el contacto ya existe en la lista destino. |
contact_11 | 500 | No se pudo insertar el contacto (error interno). |
contact_13 | 200 | Contacto agregado. |
contact_14 | 500 | No se pudo actualizar el contacto (error interno). |
contact_15 | 200 | Contacto actualizado. |
contact_16 | 400 | Parámetro inválido o contacto no definido. |
contact_17 | 409 | La lista está vinculada a una herramienta y no admite alta/edición manual de contactos. |
contact_19 | 404 / 500 | El contacto no existe (404) o falló su eliminación (500). |
agenda_01 | 200 | Lista creada (devuelve list_key). |
agenda_02 | 400 | Nombre de lista no definido. |
agenda_03 | 409 | Ya existe una lista con ese nombre en la cuenta. |
agenda_04 / agenda_05 | 500 | No se pudo crear la lista (error interno). |
agendas_01 | 200 | Consulta de listas exitosa. |
change_name_01 | 200 | Lista renombrada. |
change_name_02 / change_name_03 | 400 | Falta list_key / falta el nuevo nombre. |
change_name_04 | 500 | No se pudo renombrar la lista (error interno). |
get_contacts_01 | 200 | Consulta de contactos exitosa. |
get_contacts_02 | 400 | list_key no definido. |
delete_01 | 200 | Lista eliminada. |
err_01 | 404 / 500 | La lista no existe (404) o falló la operación (500). |
contactlist_02 | 400 | query de más de 100 caracteres. |
contactlist_03 | 200 | Búsqueda de listas exitosa. |
contactlist_04 / contactlist_05 | 400 | limit / page no numéricos. |
contactlist_06 | 400 | Pá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ódigo | Categoría | Significado |
|---|---|---|
wallets_get_01 | 200 | Consulta de monederos exitosa. |
contact_add_01 | 200 | Contacto de monedero agregado. |
balance_update_01 | 200 | Saldo del contacto actualizado. |
balance_get_01 | 200 | Consulta de saldo exitosa. |
new_sale_01 | 200 | Venta registrada. |
contact_add_02–06 | 400 | Validación al agregar contacto (falta wallet_key/phone/customer_name/usertool_id o formato inválido). |
wallet_manager_09 | 400 | El monedero solicita fecha de nacimiento y customer_birthdate falta o no tiene formato YYYY-MM-DD. |
wallet_manager_19–21 | 400 | Canal de notificación / email indefinido o con formato inválido. |
balance_update_02–08 | 400 | Validación al actualizar saldo. |
balance_get_02 / balance_get_03 | 400 | Falta wallet_key / número con formato incorrecto. |
new_sale_02–09, new_sale_11 | 400 | Validación al registrar venta (incluye falta de ticket). |
contact_add_07 / balance_update_09 / balance_get_06 / new_sale_10 | 404 | El monedero (wallet_key) no existe. |
balance_update_10 / balance_get_04 / new_sale_12 | 404 | El contacto no está registrado en el monedero. |
loyalty_contact_add_25 / loyalty_contact_add_26 | 403 | El colaborador (usertool_id) no tiene API Key o permiso sobre el monedero. |
balance_update_11 / balance_update_12 | 403 | El colaborador no tiene API Key o permiso sobre el monedero. |
new_sale_13 / new_sale_14 | 403 | El colaborador no tiene API Key o permiso sobre el monedero. |
err_toolusers_01 / err_toolusers_02 | 403 | El usertool_id no pertenece a la cuenta. |
new_sale_15 | 500 | Error interno al registrar la venta. |
err_03 | 500 | Error interno inesperado (catch genérico, en cualquier endpoint de monedero). |
Solicitud de pago (paymentrequest_)
| Código | Categoría | Significado |
|---|---|---|
paymentrequest_01 | 200 | Solicitud de pago generada o enviada. |
paymentrequest_02 | 400 | Error de validación (número, país, plantilla, mensaje, monto, nombre, email, custom o descripción). |
paymentrequest_03 | 404 | La plantilla (template) no existe o no está activa. |
paymentrequest_06 | 409 | La 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_07 | 500 | Error 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ódigo | Categoría | Significado |
|---|---|---|
subaccount_01 | 200 / 404 | Éxito (alta o archivado de subcuenta) / en /get_apikey: no se encontró la API key de la subcuenta. |
subaccount_02 | 400 / 409 | Email con formato incorrecto / en /archive: la subcuenta ya está archivada. |
subaccount_03 | 400 / 500 | Email demasiado largo / en /archive: error interno al archivar la subcuenta. |
subaccount_04 | 400 / 404 | Email requerido / en /archive: la subcuenta no existe. |
subaccount_05 a subaccount_10 | 400 | Validación de password / nombre / número (corto, largo, requerido, formato). |
subaccount_11 | 409 | El email ya está registrado en otra cuenta. |
subaccount_12 | 409 | El número ya está registrado en otra cuenta. |
subaccount_13 / subaccount_14 | 500 | Error interno al crear la subcuenta / su configuración. |
subaccount_15 | 500 | Error interno al asignar la ruta de la subcuenta. |
subaccount_credits_06 / subaccount_credits_09 | 200 | Créditos transferidos / retirados de la subcuenta. |
subaccount_credits_01 / _03 / _04 / _05 / _12 | 400 | Validación de créditos (tipo o monto no definido, decimal, ≤0, tipo inválido). |
subaccount_credits_10 | 500 | Error interno al aplicar el movimiento de créditos. |
subaccount_credits_11 | 409 | Créditos insuficientes para completar la operación. |
admin_credits_01 | 404 | La 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ódigo | Categoría | Significado |
|---|---|---|
loyalty_get_01 | 200 | Listado de tarjetas obtenido. |
loyalty_02 | 404 | El loyalty_key (hash de tarjeta) es incorrecto o no existe. |
loyalty_03 | 403 | La tarjeta de lealtad está deshabilitada. |
loyalty_contact_add_01 | 200 | Contacto de lealtad creado. |
loyalty_contact_add_02 a loyalty_contact_add_06 | 400 | Validación: loyalty_key, número (formato), nombre o email faltante/incorrecto. |
loyalty_contact_add_22 a loyalty_contact_add_24 | 400 | Canal a notificar indefinido, o email indefinido/con formato inválido. |
loyalty_contact_add_25 / loyalty_contact_add_26 | 403 | El colaborador (usertool_id) no tiene API Key o permiso sobre la tarjeta. |
loyalty_contact_add_07 | 500 | Error interno al crear el contacto de lealtad. |
loyalty_contact_get_02 / loyalty_contact_get_03 | 400 | loyalty_key no definido / número con formato incorrecto. |
loyalty_contact_get_04 | 404 | La tarjeta de lealtad no existe. |
loyalty_contact_get_05 | 404 | El contacto no está registrado en la tarjeta. |
loyalty_sale_01 | 200 | Venta registrada. |
loyalty_sale_03 a loyalty_sale_06, loyalty_sale_09, loyalty_sale_10 | 400 | Validación: loyalty_key, número, ticket o monto faltante/incorrecto. |
loyalty_sale_07 | 404 | El contacto no existe en la tarjeta. |
loyalty_sale_21 / loyalty_sale_22 | 403 | El 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ódigo | Categoría | Significado |
|---|---|---|
tool_users_01 / toolusers_01 | 200 | Listado obtenido / colaborador creado o archivado. |
err_toolusers_02 | 400 | Validación: tipo, nombre, sucursal, número o password faltante/inválido, o línea/número no soportado. |
err_toolusers_03 | 409 | Ya existe un colaborador con ese nombre o número. |
err_toolusers_04 | 500 | Error interno al crear/archivar el colaborador. |
err_toolusers_01 | 500 | Error interno inesperado al procesar la operación. |
Otros dominios
| Código | Categoría | Significado |
|---|---|---|
credit_01 | 200 | Consulta de créditos exitosa. |
report_01 / report_02 | 400 | Falta start_date / falta end_date. |
report_03 | 400 | start_date es posterior a end_date. |
report_04 | 200 | Reporte generado. |
report_05 | 429 | Límite de peticiones de reporte por cuenta excedido. |
campaigns_02 | 400 | (/campaigns) start_date es posterior a end_date. |
details_02 | 400 | campaign_id no definido o no numérico. |
details_03 | 404 | La campaña no existe. |
details_04 | 409 | La campaña aún se está procesando (aún no consultable). |
webhook_01 / webhook_02 | 200 / 500 | (/webhook/get) Configuración obtenida / error interno al leerla. |
add_webhook_01 | 200 | Webhook registrado. |
add_webhook_02 a add_webhook_06 | 400 | URL 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_08 | 500 | Error interno al guardar el webhook. |
status_webhook_01 | 200 | Estado del webhook actualizado. |
status_webhook_02 / status_webhook_03 | 400 | Estado no definido / inválido (solo 0 o 1). |
status_webhook_04 / status_webhook_05 | 500 | Error interno al actualizar el estado. |
delete_webhook_01 | 200 | Webhook eliminado. |
delete_webhook_02 / delete_webhook_03 | 500 | Error interno al eliminar el webhook. |
err_03 | 400/500 | Error genérico; el message describe la causa. |
rate_01 | 429 | Rate limit excedido. |
server_01 | 500 | Error 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. Como todas las rutas /zapier/*, reciben el opt-out automático (ver Plataformas de automatización arriba): sin header responden 200, y esos status solo llegan si envías X-Api-Http-Semantics: 1.
El destino de un webhook debe ser una dirección pública y resoluble. Se rechazan los destinos a IPs privadas o reservadas (10.x, 127.x, 192.168.x, 169.254.x), a localhost, y a hosts cuyo nombre no resuelve.
El momento del rechazo depende del endpoint:
/webhook/addvalida el destino al guardarlo y respondeadd_webhook_03— el mismo código que una URL con formato inválido, así que elmessageno distingue entre ambos casos./zapier/hooks/subscribeacepta la suscripción sin verificar el destino; la validación ocurre en el momento de la entrega. Si el destino deja de ser público, las entregas se detienen sin que ninguna respuesta de la API lo reporte.
Si un hook dejó de recibir eventos, verifica primero que su URL siga apuntando a un host público y resoluble.
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 / endpoint | Límite | Al exceder |
|---|---|---|
Producción (todos los endpoints excepto /whatsapp/send) | 100 mensajes/segundo | HTTP 429, code: rate_01 |
| Sandbox | 100 envíos de prueba/día | success: false, code: sms_17 |
/whatsapp/send | 1 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
Revisa success primero
Si success es false, no proceses la respuesta como éxito aunque el HTTP sea 200.
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.
Reintenta solo 429 y 500
Implementa reintentos con backoff exponencial únicamente en 429 y 500. Los 4xx restantes requieren corregir la petición.
No confundas un error real con un fallo de red
Bajo el comportamiento por defecto (status HTTP semántico, ver arriba), un 4xx/5xx es una respuesta real con cuerpo JSON, no una excepción de red. Varios clientes HTTP (como axios) lanzan una excepción para cualquier status ≥400 por default; si tu catch no distingue err.response de un error sin respuesta, un 429 real se ve indistinguible de un timeout o una conexión rechazada:
try {
await axios.post("https://api.smsmasivos.com.mx/v2/otp", body, { headers });
} catch (err) {
if (err.response) {
// La API SÍ respondió: err.response.status, err.response.data.code, etc.
if (err.response.data?.code === "otp_throttled") {
const waitSeconds = err.response.data.resend_available_in;
// esperar waitSeconds antes de reintentar, no reintentar de inmediato
}
} else {
// Esto sí es un fallo de red real (timeout, DNS, conexión rechazada)
}
}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.