28 feb 2025·8 min de lectura

Patrones de contrato de errores de API para mensajes claros y amigables

Diseña un contrato de errores de API con códigos estables, mensajes localizados y sugerencias útiles para la interfaz que reducen la carga del soporte y ayudan a los usuarios a recuperarse rápidamente.

Patrones de contrato de errores de API para mensajes claros y amigables

Por qué los errores vagos de la API crean problemas reales para los usuarios

Un error vago en la API no es solo una molestia técnica. Es un momento roto en el producto donde alguien se queda atascado, adivina qué hacer a continuación y a menudo abandona. Ese único "Algo salió mal" se traduce en más tickets de soporte, pérdida de usuarios y errores que nunca terminan de resolverse.

Un patrón común es este: un usuario intenta guardar un formulario, la UI muestra un toast genérico y los logs del backend muestran la causa real ("violación de restricción única en email"). El usuario no sabe qué cambiar. Soporte no puede ayudar porque no hay un código fiable para buscar en los registros. El mismo problema se reporta con capturas de pantalla y descripciones distintas, y no hay una forma limpia de agruparlo.

Los detalles que necesitan los desarrolladores y las necesidades de los usuarios no son lo mismo. Los ingenieros necesitan contexto preciso de la falla (qué campo, qué servicio, qué timeout). Los usuarios necesitan un paso claro a seguir: "Ese correo ya está en uso. Prueba iniciar sesión o usa otro correo." Mezclar ambos suele llevar a divulgar internals de forma insegura o a mensajes inútiles que lo ocultan todo.

Para eso sirve un contrato de errores de API. La meta no es "más errores", sino una estructura consistente de modo que:

  • los clientes puedan interpretar fallos de forma fiable en todos los endpoints
  • los usuarios vean mensajes seguros y en lenguaje claro que les ayuden a recuperarse
  • soporte y QA puedan identificar el problema exacto con un código estable
  • los ingenieros obtengan diagnósticos sin exponer detalles sensibles

La consistencia es toda la idea. Si un endpoint devuelve error: "Invalid" y otro devuelve message: "Bad request", la UI no puede guiar a los usuarios y tu equipo no puede medir lo que ocurre. Un contrato claro hace que los errores sean predecibles, buscables y más fáciles de arreglar, aunque las causas subyacentes cambien.

Qué significa en la práctica un contrato de errores consistente

Un contrato de errores de API es una promesa: cuando algo falla, tu API responde con una forma familiar con campos y códigos predecibles, sin importar qué endpoint falló.

No es un volcado de depuración ni un sustituto de los logs. El contrato es aquello en lo que las apps cliente pueden confiar de forma segura. Los logs son donde guardas trazas de pila, detalles SQL y cualquier cosa sensible.

En la práctica, un buen contrato mantiene algunas cosas estables: la forma de la respuesta entre endpoints (tanto 4xx como 5xx), códigos de error legibles por máquina que no cambian de significado, y un mensaje seguro para el usuario. También ayuda a soporte incluyendo un identificador de petición/traza, y puede incluir pistas simples para la UI, como si el usuario debe reintentar o corregir un campo.

La consistencia solo funciona si decides dónde se aplica. Los equipos suelen empezar con un punto de aplicación y expandir después: un API gateway que normalice errores, middleware que envuelva fallos no capturados, una librería compartida que construya el mismo objeto de error o un manejador de excepciones a nivel de framework por servicio.

La expectativa clave es simple: cada endpoint devuelve o bien una forma de éxito o bien el contrato de error para cada modo de falla. Eso incluye errores de validación, fallos de autenticación, límites de tasa, timeouts y caídas en servicios upstream.

Una forma de respuesta de error sencilla que escala

Un buen contrato de errores de API se mantiene pequeño, predecible y útil tanto para personas como para software. Cuando un cliente siempre encuentra los mismos campos, soporte deja de adivinar y la UI puede ofrecer ayuda más clara.

Aquí tienes una forma JSON mínima que funciona para la mayoría de productos (y escala a medida que añades endpoints):

{
  "status": 400,
  "code": "AUTH.INVALID_EMAIL",
  "message": "Enter a valid email address.",
  "details": {
    "fields": {
      "email": "invalid_email"
    },
    "action": "fix_input",
    "retryable": false
  },
  "trace_id": "01HZYX8K9Q2..."
}

Para mantener el contrato estable, trata cada parte como una promesa separada:

  • status es para el comportamiento HTTP y categorías amplias.
  • code es el identificador estable legible por máquina (el núcleo de tu contrato de errores de API).
  • message es texto seguro para la UI (y algo que puedes localizar más tarde).
  • details contiene pistas estructuradas: problemas por campo, qué hacer a continuación y si tiene sentido reintentar.
  • trace_id permite a soporte encontrar la falla exacta en servidor sin exponer internals.

Mantén el contenido dirigido al usuario separado de la información de depuración interna. Si necesitas diagnósticos adicionales, regístralos server-side indexados por trace_id (no en la respuesta). Eso evita filtrar datos sensibles mientras facilita investigar problemas.

Para errores por campo, details.fields es un patrón simple: las claves coinciden con los nombres de entrada y los valores contienen razones cortas como invalid_email o too_short. Añade orientación solo cuando ayude. Para timeouts, action: "retry_later" suele ser suficiente. Para caídas temporales, retryable: true ayuda a los clientes a decidir si mostrar un botón de reintento.

Una nota antes de implementar: algunos equipos envuelven errores en un objeto error (por ejemplo, { "error": { ... } }) mientras que otros mantienen los campos en el nivel superior. Cualquiera de los dos puede funcionar. Lo importante es elegir un sobre y mantenerlo consistente en todas partes.

Códigos de error estables: patrones que no rompen a los clientes

Los códigos de error estables son la columna vertebral de un contrato de errores de API. Permiten que apps, dashboards y equipos de soporte reconozcan un problema incluso cuando cambias el texto, añades campos o mejoras la UI.

Una convención de nombres práctica es:

DOMAIN.ACTION.REASON

Ejemplos: AUTH.LOGIN.INVALID_PASSWORD, BILLING.PAYMENT.CARD_DECLINED, PROFILE.UPDATE.EMAIL_TAKEN. Mantén los dominios pequeños y familiares (AUTH, BILLING, FILES). Usa verbos de acción claros (CREATE, UPDATE, PAY).

Trata los códigos como endpoints: una vez públicos, no deben cambiar de significado. El texto mostrado al usuario puede mejorar con el tiempo (mejor tono, pasos más claros, nuevos idiomas), pero el código debe permanecer igual para que los clientes no fallen y las analíticas sigan limpias.

También vale decidir qué códigos son públicos frente a internos. Una regla simple: los códigos públicos deben ser seguros para mostrar, estables, documentados y usados por la UI. Los códigos internos pertenecen a los logs para depuración (nombres de bases de datos, detalles de proveedores, trazas). Un código público puede mapear a muchas causas internas, especialmente cuando una dependencia puede fallar de distintas maneras.

La deprecación funciona mejor cuando es aburrida. Si debes reemplazar un código, no lo reutilices silenciosamente con otro significado. Introduce un código nuevo y marca el antiguo como deprecated. Da a los clientes una ventana de solapamiento donde ambos puedan aparecer. Si incluyes un campo como deprecated_by, apunta al nuevo código (no a una URL).

Por ejemplo, conserva BILLING.PAYMENT.CARD_DECLINED incluso si más tarde mejoras el texto y lo divides en "Prueba otra tarjeta" vs "Llama a tu banco". El código sigue estable mientras la orientación evoluciona.

Mensajes localizados sin perder consistencia

Haz que los problemas sean fáciles de rastrear
Añade trace IDs a las respuestas para que soporte encuentre el evento exacto en el servidor
Comenzar

La localización se complica cuando la API devuelve frases completas y los clientes las tratan como lógica. Un enfoque mejor es mantener el contrato estable y traducir el texto en el último tramo. Así, el mismo error significa lo mismo independientemente del idioma, el dispositivo o la versión de la app.

Primero, decide dónde viven las traducciones. Si necesitas una fuente única para web, móvil y herramientas de soporte, los mensajes server-side pueden ayudar. Si la UI necesita control fino sobre tono y formato, las traducciones en el cliente suelen ser más sencillas. Muchos equipos usan un híbrido: la API devuelve un código estable más una clave de mensaje y parámetros, y el cliente elige el texto final.

Para un contrato de errores, las claves de mensaje suelen ser más seguras que frases codificadas. La API puede devolver algo como message_key: "auth.too_many_attempts" con params: {"retry_after_seconds": 300}. La UI traduce y formatea esto sin cambiar el significado.

La pluralización y las caídas por defecto importan más de lo que se espera. Usa una configuración i18n que soporte las reglas de pluralización por locale, no solo el estilo inglés de "1 vs muchos". Define una cadena de fallback (por ejemplo: fr-CA -> fr -> en) para que las cadenas faltantes no se conviertan en pantallas en blanco.

Una buena regla es tratar el texto traducido como estrictamente dirigido al usuario. No pongas trazas de pila, IDs internos o detalles crudos de "por qué falló" en cadenas localizadas. Mantén los detalles sensibles en campos no mostrados (o en logs) y ofrece a los usuarios mensajes seguros y accionables.

Convertir fallos del backend en pistas que la UI pueda seguir

Haz que los errores guíen a los usuarios
Mapea fallos del backend a acciones: corregir entrada, reintentar más tarde o contactar soporte
Probar ahora

La mayoría de los errores del backend son útiles para ingenieros, pero con demasiada frecuencia terminan en pantalla como "Algo salió mal". Un buen contrato de errores convierte esas fallas en pasos claros a seguir sin filtrar detalles sensibles.

Un enfoque simple es mapear fallos a una de tres acciones para el usuario: corregir entrada, reintentar o contactar soporte. Eso mantiene la UI consistente en web y móvil aunque el backend tenga muchos modos de falla.

  • Corregir entrada: validación fallida, formato incorrecto, campo requerido faltante.
  • Reintentar: timeouts, problemas temporales upstream, límites de tasa.
  • Contactar soporte: problemas de permisos, conflictos que el usuario no puede resolver, errores internos inesperados.

Las pistas por campo importan más que mensajes largos. Cuando el backend sabe qué entrada falló, devuelve un puntero legible por máquina (por ejemplo, un nombre de campo como email o card_number) y una razón corta que la UI pueda mostrar en línea. Si varios campos están mal, devuelve todos para que el usuario pueda corregirlos de una vez.

También ayuda adaptar el patrón de UI a la situación. Un toast está bien para un mensaje temporal de reintento. Los errores de entrada deben mostrarse en línea. Los bloqueos de cuenta o pago suelen necesitar un diálogo modal.

Incluye contexto de solución de problemas de forma segura y consistente: trace_id, una marca de tiempo si ya la tienes, y un siguiente paso sugerido como un tiempo de reintento. Así, un timeout del proveedor de pagos puede mostrar "El servicio de pagos está lento. Por favor, intenta de nuevo" más un botón de reintento, mientras soporte usa el mismo trace_id para encontrar la falla server-side.

Paso a paso: desplegar el contrato de extremo a extremo

Desplegar un contrato de errores de API funciona mejor si lo tratas como un pequeño cambio de producto, no como un refactor. Hazlo incremental e involucra a soporte y equipos de UI desde el principio.

Una secuencia de rollout que mejora los mensajes visibles al usuario sin romper clientes:

  1. Inventario de lo que tienes (agrupa por dominio). Exporta respuestas de error reales de los logs y agrúpalas en cubos como auth, signup, billing, subida de archivos y permisos. Busca repeticiones, mensajes poco claros y sitios donde la misma falla aparece en cinco formas distintas.
  2. Define el esquema y comparte ejemplos. Documenta la forma de la respuesta, campos obligatorios y ejemplos por dominio. Incluye nombres de código estables, una clave de mensaje para localización y una sección opcional de pistas para la UI.
  3. Implementa un mapeador de errores central. Pon el formateo en un solo lugar para que cada endpoint devuelva la misma estructura. En un backend generado (o uno sin código como AppMaster), esto suele significar un paso compartido "mapear error a respuesta" que cada endpoint o proceso de negocio llama.
  4. Actualiza la UI para interpretar códigos y mostrar pistas. Haz que la UI dependa de códigos, no del texto del mensaje. Usa códigos para decidir si resaltar un campo, mostrar una acción de reintento o sugerir contactar soporte.
  5. Añade logging y un trace_id que soporte pueda pedir. Genera un trace_id para cada petición, regístralo server-side con los detalles crudos de la falla y devuélvelo en la respuesta de error para que los usuarios puedan copiarlo.

Después del primer pase, mantén el contrato estable con algunos artefactos ligeros: un catálogo compartido de códigos por dominio, archivos de traducción para mensajes localizados, una tabla simple de mapeo código -> pista UI/acción siguiente y un playbook de soporte que empiece con "envíanos tu trace_id".

Si tienes clientes legacy, conserva campos antiguos por una corta ventana de deprecación, pero deja de crear nuevas formas únicas inmediatamente.

Errores comunes que hacen los errores más difíciles de soportar

Convierte esquemas en APIs reales
Modela tus datos y genera un backend que devuelva formatos de error predecibles
Crear proyecto

La mayoría del dolor de soporte no viene de "usuarios malos". Viene de la ambigüedad. Cuando tu contrato de errores es inconsistente, cada equipo inventa su propia interpretación y los usuarios reciben mensajes que no pueden actuar.

Una trampa común es tratar los códigos de estado HTTP como toda la historia. "400" o "500" te dice casi nada sobre lo que el usuario debe hacer a continuación. Los códigos de estado ayudan con el transporte y la clasificación amplia, pero aún necesitas un código estable a nivel de aplicación que mantenga su significado entre versiones.

Otro error es cambiar lo que significa un código con el tiempo. Si PAYMENT_FAILED antes significaba "tarjeta declinada" y luego pasa a significar "Stripe no funciona", tu UI y documentación quedan obsoletas sin que nadie lo note. Soporte recibe tickets como "Probé tres tarjetas y sigue fallando" cuando el problema real es una caída.

Devolver texto de excepción crudo (o peor, trazas de pila) también es tentador porque es rápido. Rara vez es útil para los usuarios y puede filtrar detalles internos. Mantén los diagnósticos crudos en logs, no en respuestas mostradas a personas.

Algunos patrones causan ruido consistentemente:

  • Usar en exceso un código comodín como UNKNOWN_ERROR elimina cualquier posibilidad de guiar al usuario.
  • Crear demasiados códigos sin una taxonomía clara dificulta dashboards y playbooks.
  • Mezclar texto dirigido al usuario con diagnósticos de desarrollador en el mismo campo hace que la localización y las pistas UI sean frágiles.

Una regla simple ayuda: un código estable por decisión del usuario. Si el usuario puede arreglarlo cambiando una entrada, usa un código específico y una pista clara. Si no puede (por ejemplo, una caída de proveedor), conserva el código estable y devuelve un mensaje seguro más una acción como "Intenta de nuevo más tarde" y un ID de correlación para soporte.

Lista de verificación rápida antes del lanzamiento

Antes de publicar, trata los errores como una característica del producto. Cuando algo falla, el usuario debe saber qué hacer a continuación, soporte debe poder encontrar el evento exacto y los clientes no deben romperse cuando el backend cambia.

  • Misma forma en todas partes: cada endpoint (incluyendo auth, webhooks y subida de archivos) devuelve un sobre de error consistente.
  • Códigos estables y con responsable: cada código tiene un dueño claro (Payments, Auth, Billing). No reutilices un código para otro significado.
  • Mensajes seguros y localizables: el texto para el usuario es corto y nunca incluye secretos (tokens, datos completos de tarjeta, SQL crudo, trazas de pila).
  • Acción UI clara: para los tipos de falla más frecuentes, la UI muestra un paso obvio (intentar de nuevo, actualizar un campo, usar otro método de pago, contactar soporte).
  • Trazabilidad para soporte: cada respuesta de error incluye un trace_id (o similar) que soporte puede pedir, y tu logging/monitoring puede encontrar la historia completa rápidamente.

Prueba unos flujos realistas de extremo a extremo: un formulario con entrada inválida, una sesión expirada, un límite de tasa y una caída de un tercero. Si no puedes explicar la falla en una frase y apuntar al trace_id exacto en los logs, no estás listo para lanzar.

Ejemplo: errores de registro y pago que los usuarios pueden resolver

Crea APIs con errores claros
Diseña una respuesta de error consistente y reutilízala en todos los endpoints
Probar AppMaster

Un buen contrato de errores de API hace que la misma falla sea comprensible en tres lugares: tu UI web, tu app móvil y el email automatizado que el sistema pueda enviar tras un intento fallido. También da a soporte detalle suficiente para ayudar sin pedir al usuario capturas de pantalla.

Registro: error de validación que el usuario puede corregir

Un usuario introduce un email como sam@ y pulsa Registrarse. La API devuelve un código estable y una pista por campo, de modo que todos los clientes puedan resaltar el mismo input.

{
  "error": {
    "code": "AUTH.EMAIL_INVALID",
    "message": "Enter a valid email address.",
    "i18n_key": "auth.email_invalid",
    "params": { "field": "email" },
    "ui": { "field": "email", "action": "focus" },
    "trace_id": "4f2c1d..."
  }
}

En web, muestras el mensaje debajo del campo de email. En móvil, enfocas el campo email y muestras una pequeña franja. En el email, puedes decir: "No pudimos crear tu cuenta porque la dirección de correo parece incompleta." Mismo código, mismo significado.

Pago: fallo con una explicación segura

Un pago con tarjeta falla. El usuario necesita orientación, pero no deberías exponer internals del procesador. Tu contrato puede separar lo que ve el usuario de lo que soporte puede verificar.

{
  "error": {
    "code": "PAYMENT.DECLINED",
    "message": "Your payment was declined. Try another card or contact your bank.",
    "i18n_key": "payment.declined",
    "params": { "retry_after_sec": 0 },
    "ui": { "action": "show_payment_methods" },
    "trace_id": "b9a0e3..."
  }
}

Soporte puede pedir el trace_id, luego verificar qué código estable se devolvió, si la declinación es definitiva o retryable, a qué cuenta y monto pertenecía el intento y si se envió la pista UI. Aquí es donde un contrato de errores de API paga: tu web, iOS/Android y flujos de email se mantienen consistentes incluso cuando cambia el proveedor de backend o los detalles internos.

Probar y monitorizar tu contrato de errores a lo largo del tiempo

Lanza tu API a cualquier parte
Despliega en AppMaster Cloud o en tu propio entorno AWS, Azure o Google Cloud
Crear app

Un contrato de errores de API no está "hecho" cuando lo lanzas. Está hecho cuando el mismo código de error conduce consistentemente a la misma acción del usuario, incluso tras meses de refactors y nuevas funcionalidades.

Empieza probando desde el exterior, como un cliente real. Para cada código de error que soportes, escribe al menos una petición que lo desencadene y asegúrate del comportamiento del que dependes: status HTTP, código, clave de localización y campos de pista UI (como qué campo resaltar).

Un conjunto pequeño de tests cubre la mayor parte del riesgo:

  • una petición happy-path junto a cada caso de error (para detectar sobrevalidaciones accidentales)
  • una prueba por cada código estable para comprobar las pistas UI o el mapeo de campos
  • una prueba que asegure que fallos desconocidos devuelven un código genérico seguro
  • una prueba que verifique que existen claves de localización para cada idioma soportado
  • una prueba que garantice que detalles sensibles nunca aparecen en respuestas al cliente

La monitorización es cómo detectas regresiones que los tests no atrapan. Haz seguimiento del recuento de códigos de error con el tiempo y alerta sobre picos repentinos (por ejemplo, un código de pago que se duplica tras un release). También vigila la aparición de códigos nuevos en producción. Si aparece un código que no está en tu lista documentada, probablemente alguien haya omitido el contrato.

Decide pronto qué queda interno y qué va a los clientes. Una división práctica: los clientes obtienen un código estable, una clave de localización y una pista de acción; los logs obtienen la excepción cruda, traza de pila, request ID y fallos de dependencias (base de datos, proveedor de pagos, gateway de email).

Una vez al mes, revisa errores con conversaciones reales de soporte. Elige los cinco códigos principales por volumen y lee algunos tickets o chats para cada uno. Si los usuarios siguen haciendo la misma pregunta de seguimiento, la pista UI falta un paso o el mensaje es demasiado vago.

Próximos pasos: aplica el patrón en tu producto y procesos

Empieza donde la confusión sea más cara: los pasos con mayor abandono (a menudo registro, checkout o subida de archivos) y los errores que generan más tickets. Estandariza esos primero para ver impacto en un sprint.

Una forma práctica de mantener el rollout enfocado es:

  • escoger los 10 errores que más carga de soporte generan y asignar códigos estables y valores por defecto seguros
  • definir mapeos código -> pista UI -> acción siguiente por superficie (web, móvil, admin)
  • hacer del contrato el estándar para nuevos endpoints y tratar campos faltantes como un fallo en la revisión
  • mantener un pequeño playbook interno: qué significa cada código, qué pide soporte y quién corrige
  • medir algunos indicadores: tasa de error por código, conteo de "unknown error" y volumen de tickets ligados a cada código

Si construyes con AppMaster (appmaster.io), vale la pena incorporarlo desde temprano: define una forma de error consistente para tus endpoints y mapea códigos estables a mensajes UI en tus pantallas web y móviles para que los usuarios obtengan el mismo significado en todas partes.

Un ejemplo sencillo: si soporte recibe quejas de "Pago falló", estandarizar te permite que la UI muestre "Tarjeta declinada" con una pista para intentar otra tarjeta para un código, y "Sistema de pagos temporalmente no disponible" con acción de reintento para otro. Soporte puede pedir el trace_id en lugar de adivinar.

Pon una limpieza recurrente en el calendario. Retira códigos no usados, afina mensajes vagos y añade texto localizado donde haya volumen real. El contrato se mantiene estable mientras el producto sigue cambiando.

Fácil de empezar
Crea algo sorprendente

Experimente con AppMaster con plan gratuito.
Cuando esté listo, puede elegir la suscripción adecuada.

Empieza