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.
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ó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). |
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. |
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). |
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. 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ó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 (1000 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_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. 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 / endpoint | Límite | Al exceder |
|---|---|---|
Producción (todos los endpoints excepto /whatsapp/send) | 100 mensajes/segundo | HTTP 429, code: rate_01 |
| Sandbox | 1000 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.
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.