Manejo de Errores en API REST: Buenas Prácticas, Códigos de Estado, RFC 9457 y Errores Reintentables

Domina el manejo de errores de API para REST: elige los códigos de estado correctos, devuelve los detalles del problema de RFC 9457, marca los errores reintentables y prueba cada fallo con Apidog.

INEZA Felin-Michel

INEZA Felin-Michel

31 August 2026

Manejo de Errores en API REST: Buenas Prácticas, Códigos de Estado, RFC 9457 y Errores Reintentables

Apidog para empresas

Despliegue local

SSO & RBAC

Conforme con SOC 2

Explorar Apidog Enterprise

Las respuestas de error de tu API son parte de su contrato. Los clientes las analizan, la lógica de reintento se basa en ellas, y los ingenieros de soporte las buscan a las 2 a.m. Sin embargo, la mayoría de los equipos diseñan la ruta feliz en detalle y dejan que los errores surjan de lo que el framework hace por defecto. Así es como terminas con tres formas de error diferentes en una misma API, una respuesta 200 que envuelve "success": false, y un rastreo de pila que filtra el esquema de tu base de datos a la internet pública.

Esta guía cubre las mejores prácticas de manejo de errores de API para servicios REST de principio a fin: elegir el código de estado correcto, estandarizar un solo cuerpo de error con RFC 9457 Problem Details, separar códigos legibles por máquina de mensajes humanos, marcar errores como reintentables, y mantener los secretos fuera de las respuestas. Se basa en nuestro desglose de qué códigos de estado HTTP deben usar las API REST y añade las decisiones a nivel de contrato que esa guía deja abiertas. También verás cómo probar cada ruta de falla en Apidog, porque un contrato de error que nunca pruebas es un contrato que no tienes.

Comienza con el código de estado, no con el cuerpo

HTTP ya te proporciona una primera capa de semántica de errores de forma gratuita. RFC 9110 define las familias de códigos de estado: 4xx significa que el cliente hizo algo mal y repetir la misma solicitud volverá a fallar; 5xx significa que el servidor falló y la solicitud del cliente pudo haber sido correcta. Aclara esta división antes de escribir una sola línea de cuerpo de error, porque los clientes genéricos, los proxies, las cachés y las bibliotecas de reintento se ramifican en ella sin siquiera leer tu JSON.

Los errores más comunes se agrupan en torno a un puñado de pares de aspecto similar. Mantén abierta la referencia de códigos de estado HTTP de MDN mientras diseñas, y usa esta tabla de decisiones para los códigos que confunden a los equipos.

Situación Usar No usar Por qué
Solicitud mal formada: JSON roto, tipo de contenido incorrecto, campo requerido faltante 400 Bad Request 422 El servidor no puede analizar o entender la solicitud en absoluto
Solicitud bien formada que viola reglas semánticas: cantidad negativa, moneda no soportada 422 Unprocessable Content 400 La sintaxis es correcta; los valores no lo son
Sin credenciales, o token caducado/inválido 401 Unauthorized 403 El cliente no ha probado quién es. Enviar WWW-Authenticate
Credenciales válidas, permisos insuficientes 403 Forbidden 401 La identidad es conocida; el acceso es denegado. Volver a autenticar no ayudará
El recurso nunca existió, o no confirmarás que existe 404 Not Found 410 Valor por defecto seguro; también oculta recursos de sondeos no autorizados
El recurso existió y fue eliminado deliberada y permanentemente 410 Gone 404 Les dice a los clientes y rastreadores que eliminen sus referencias
Conflicto de estado: clave duplicada, versión obsoleta, colisión de edición 409 Conflict 400 La solicitud es válida pero choca con el estado actual del recurso
El cliente excedió un límite de tasa 429 Too Many Requests 503 Siempre incluir Retry-After para que los clientes retrocedan correctamente
Excepción no manejada en tu código 500 Internal Server Error 502 Tu servidor se rompió
El servicio ascendente devolvió basura a tu puerta de enlace 502 Bad Gateway 500 El fallo está corriente abajo del borde, no en él
El servidor está sobrecargado o en mantenimiento 503 Service Unavailable 500 Temporal por definición; añade Retry-After cuando puedas
El servicio ascendente agotó el tiempo de espera 504 Gateway Timeout 500 Distingue "dependencia lenta" de "código roto"

Dos de ellos merecen un énfasis especial. Primero, 401 vs 403 es un límite de seguridad, no una elección de estilo: devolver 403 a un llamador no autenticado filtra el hecho de que el recurso existe. Segundo, 429 sin Retry-After entrena a los clientes para que te ataquen en bucles cerrados. Si limitas la tasa, y deberías hacerlo, combina el estado con una señal concreta de retroceso; nuestra guía sobre limitación de tasa de API cubre las matemáticas de los encabezados y los algoritmos detrás de ella.

Una forma de cuerpo de error: RFC 9457 Problem Details

Una vez que el código de estado es correcto, cada error que tu API devuelva debe compartir un tipo de medio y un esquema. La respuesta estándar es RFC 9457 Problem Details, servida como application/problem+json. Define cinco miembros principales: type (una URI que identifica la categoría del error), title (un breve resumen humano), status (el código HTTP, repetido por conveniencia), detail (lo que salió mal en esta ocurrencia), e instance (una URI para este fallo específico). Cualquier otra cosa va en miembros de extensión que definas tú mismo.

No volveremos a derivar la especificación aquí; nuestro explicador de RFC 9457 detalla cada miembro, las reglas de registro y cómo anula a RFC 7807. Lo que importa para tu contrato es el patrón: envoltorio estándar, extensiones personalizadas. Aquí tienes un fallo de validación en un endpoint de pagos.

POST /v1/payments HTTP/1.1
Content-Type: application/json

{ "amount": -1400, "currency": "USD", "source": "card_8xKt2" }
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json

{
  "type": "https://api.example.com/problems/validation-error",
  "title": "Request validation failed",
  "status": 422,
  "detail": "One or more fields failed validation.",
  "instance": "/v1/payments/requests/req_9f3c1a7b",
  "code": "PAYMENT_VALIDATION_FAILED",
  "errors": [
    {
      "field": "amount",
      "code": "AMOUNT_NOT_POSITIVE",
      "message": "amount must be a positive integer in minor units"
    }
  ],
  "request_id": "req_9f3c1a7b"
}

El array errors[] es un miembro de extensión, y es el que más les gusta a los clientes: permite que un frontend mapee cada fallo al campo de formulario exacto en lugar de mostrar un mensaje vago. Mantén las rutas de campo en un formato estable (JSON Pointer o rutas con puntos, elige una) para que el código del cliente pueda vincularlas programáticamente.

Una regla te ahorra la mayor parte del dolor: devuelve esta forma para cada error, incluidos los que genera tu framework o puerta de enlace. Un cliente que recibe Problem Details de tus manejadores pero HTML de la página 502 de tu equilibrador de carga todavía tiene que escribir dos analizadores.

Códigos legibles por máquina vs. mensajes humanos

Observa que el ejemplo incluye campos code y message. Eso es deliberado. Sirven a diferentes audiencias y nunca deben fusionarse en una sola cadena.

Los códigos legibles por máquina (AMOUNT_NOT_POSITIVE, CURRENCY_UNSUPPORTED, IDEMPOTENCY_KEY_REUSED) son contrato. Los clientes se ramifican en ellos, por lo que deben ser estables, documentados y enumerables. Nunca hagas que los clientes analicen prosa; en el momento en que alguien escribe if (message.includes("positive")), tu edición de copia se convierte en un cambio que rompe la compatibilidad.

Los mensajes humanos son lo contrario: libres de mejorar en cualquier momento, escritos para un desarrollador que lee registros y nunca son críticos para el funcionamiento. Indica qué falló y cómo solucionarlo: "la cantidad debe ser un entero positivo en unidades menores" es mejor que "cantidad inválida". Si localizas, localiza el mensaje y deja el código como está.

Esta división importa aún más ahora que los consumidores de API incluyen agentes autónomos. Los clientes basados en LLM se recuperan mucho mejor de errores estructurados y autoexplicativos; cubrimos ese ángulo en diseño de errores de API para agentes de IA.

Lo que nunca va en una respuesta de error

Las respuestas de error son un canal de reconocimiento favorito para los atacantes, porque las fallas no manejadas tienden a ser muy detalladas. Tu middleware de errores debe garantizar que ninguno de los siguientes elementos llegue jamás a un cliente:

El patrón es simple: captura todo en el límite, registra la excepción completa en el lado del servidor con un ID de solicitud y devuelve un cuerpo genérico de Problem Details con ese mismo ID. El cliente recibe "detail": "An internal error occurred", "request_id": "req_51ad0", tus registros obtienen la verdad y el soporte puede unir ambos.

Marcar errores como reintentables o terminales

Cada error que devuelves responde a una pregunta que el cliente está a punto de hacer: ¿debo intentarlo de nuevo? Integra la respuesta en el contrato en lugar de dejar que cada equipo cliente adivine.

Los códigos de estado llevan la semántica predeterminada. 429, 502, 503 y 504 son reintentables con retroceso exponencial y fluctuación. 500 es ambiguo pero generalmente vale un reintento cauteloso. Casi todos los demás códigos 4xx son terminales: reintentar un 401, 403, 404 o 422 con la misma solicitud desperdicia cuota y contamina los registros. Los tiempos de espera merecen su propio cuidado, ya que la solicitud puede haber tenido éxito después de que el cliente se rindiera; ese es el clásico problema de tiempo de espera de solicitud 408, y es por eso que los puntos finales de mutación deben aceptar claves de idempotencia para que un pago reintentado no se pueda cobrar dos veces.

También puedes hacer explícita la reintentabilidad con un miembro de extensión:

{
  "type": "https://api.example.com/problems/rate-limited",
  "title": "Too many requests",
  "status": 429,
  "code": "RATE_LIMITED",
  "retryable": true,
  "retry_after_seconds": 30
}

Una bandera explícita retryable te permite anular los valores predeterminados cuando lo necesitas, como marcar un subcódigo 500 específico como terminal porque al reintentarlo se corrompe el estado. Documenta la bandera una vez y cada SDK de cliente que distribuyas obtendrá un comportamiento uniforme de retroceso.

IDs de correlación y versionado de contratos de error

Dos decisiones menores completan el contrato, y ambas son baratas ahora, costosas después.

Asigna un ID a cada solicitud. Acepta un encabezado X-Request-Id entrante (o genera uno), imprímelo en cada línea de registro y repítelo en cada cuerpo de error como request_id. Cuando un cliente pega un error en un ticket de soporte, ese único campo convierte una hora de búsqueda de registros en una sola consulta. En configuraciones distribuidas, propaga un traceparent de W3C junto con él para que el ID siga la solicitud a través de los servicios.

Versiona tu contrato de errores como la propia API. Añadir un nuevo miembro de extensión o un nuevo código de error es seguro. Renombrar errors[].field, cambiar el significado de un código o pasar de una forma ad-hoc a Problem Details es un cambio disruptivo, y rompe las rutas de código que los equipos menos prueban. La URI type te da un mecanismo limpio: mantén las URI de tipo antiguas estables para siempre, introduce nuevas para semánticas nuevas, y declara en tu documentación que los miembros de extensión desconocidos y los códigos desconocidos deben ser ignorados, no tratados como fallos. Esa cláusula de compatibilidad hacia adelante es lo que te permite evolucionar sin una v2.

Prueba cada ruta de error en Apidog

Aquí está la incómoda verdad: los contratos de error se pudren porque nada los ejercita. La ruta feliz se ejecuta en cada demo; la rama 422 se ejecuta cuando un cliente la activa. La solución es hacer que los casos de fallo sean ciudadanos de primera clase en tu conjunto de pruebas, y aquí es donde Apidog se gana su lugar en el flujo de trabajo.

Dos características se mapean directamente a este problema.

Escenarios de prueba para el lado del servidor. Para cada endpoint, construye un escenario por caso de fallo: autenticación faltante espera 401, rol insuficiente espera 403, cantidad negativa espera 422 con errors[0].code igual a AMOUNT_NOT_POSITIVE, tráfico en ráfaga espera 429 con un encabezado Retry-After. Las aserciones visuales de Apidog verifican el estado, los encabezados y los campos del cuerpo sin scripting, y puedes validar toda la carga útil contra tu esquema JSON de Problem Details para que cualquier desviación en la forma del error falle en CI, no en producción. Nuestra guía de aserciones de API muestra los patrones de aserción en detalle.

Servidores simulados para el lado del cliente. Tus equipos de frontend y SDK necesitan construir contra respuestas 4xx y 5xx antes de que el backend pueda producirlas bajo demanda. Los servidores simulados de Apidog devuelven los cuerpos exactos de Problem Details de tu especificación de API, para que puedas simular un 503 con Retry-After: 120, un 409 por envío doble, o una carga útil de validación completa de errors[], luego observa cómo el cliente renderiza y reintenta. Sin un stub de Express hecho a mano, sin comentar código de backend para forzar un fallo.

Diseña el contrato de errores, codifícalo como escenarios y simulaciones, e integra ambos en CI. Descarga Apidog y pruébalo gratis; importar una especificación OpenAPI existente te permite obtener respuestas de error simulables en pocos minutos.

botón

Preguntas Frecuentes

¿Debo usar 400 o 422 para errores de validación?

Usa 400 cuando la solicitud está mal formada y el servidor no puede entenderla: JSON inválido, tipo de contenido incorrecto, un campo requerido faltante. Usa 422 cuando la solicitud se analiza correctamente pero los valores rompen tus reglas de dominio, como una cantidad de pago negativa o una moneda no compatible. El beneficio práctico es diagnóstico: un 422 le dice al cliente "arregla tus datos", mientras que un 400 dice "arregla el formato de tu solicitud". Cualquiera que sea la división que elijas, aplícala consistentemente en cada endpoint.

¿Qué es application/problem+json?

Es el tipo de medio definido por RFC 9457 para Problem Details, el formato estándar de errores JSON para API HTTP. Una respuesta con este tipo de contenido incluye miembros type, title, status, detail e instance, además de cualquier extensión que definas, como un array errors[] para fallos de validación a nivel de campo. Usar el tipo de medio registrado permite que los clientes genéricos y el middleware reconozcan tus errores sin una configuración personalizada. Nuestro explicador de RFC 9457 cubre la especificación completa.

¿Qué errores HTTP deben reintentar automáticamente los clientes?

Reintenta 429, 502, 503 y 504 con retroceso exponencial más jitter, respetando Retry-After cuando esté presente. Trata el 500 como digno de un reintento cuidadoso. No reintentes otras respuestas 4xx; la solicitud fallará de la misma manera cada vez. Para endpoints que modifican datos, combina los reintentos con claves de idempotencia para que una solicitud repetida no pueda cobrar dos veces o crear algo dos veces.

¿Cómo pruebo las respuestas de error de la API sin romper mi backend?

Simúlalos. Apunta tu cliente a un servidor simulado de Apidog que devuelva los cuerpos exactos 4xx y 5xx de tu especificación, luego verifica la renderización y el comportamiento de reintento contra cada uno. En el lado del servidor, escribe escenarios de prueba que envíen cargas útiles inválidas, autenticación faltante y tráfico en ráfaga, luego aserta sobre códigos de estado, encabezados y el esquema del cuerpo del error. Ambas partes se ejecutan en CI, por lo que el contrato de errores se mantiene honesto sin que nadie fuerce manualmente los fallos.

Practica el diseño de API en Apidog

Descubre una forma más fácil de construir y usar APIs