Skip to content
StackPractices
intermediate Por Mathias Paulenko

Manejar Errores en APIs

Patrones para un manejo de errores de API consistente y predecible en varios lenguajes y frameworks.

Temas: api

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Overview

El manejo de errores es lo que separa a las APIs fiableas de las frágiles. Una respuesta de error bien diseñada le dice al cliente exactamente qué salió mal, qué hacer al respecto y cómo evitarlo en el futuro, sin filtrar detalles internos de implementación.

A continuacion se cubre el formato estándar de respuesta de error (RFC 7807 Problem Details), la selección correcta de códigos de estado HTTP y patrones de implementación idiomáticos en Python, JavaScript y Java.

When to Use

Usa esta receta cuando:

  • Construyas o refactores una API REST de la que clientes dependan
  • Estandarices respuestas de error entre múltiples servicios backend
  • Documentes modos de falla para consumidores de la API
  • Diseñes middleware de manejo de errores o mapeadores de excepciones

Solution

Python (FastAPI)

from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(ValueError)
async def value_error_handler(request, exc):
    return JSONResponse(
        status_code=400,
        content={
            "type": "https://api.example.com/errors/invalid-input",
            "title": "Entrada Inválida",
            "detail": str(exc),
            "status": 400,
        },
    )

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    if user_id <= 0:
        raise HTTPException(
            status_code=404,
            detail={
                "type": "https://api.example.com/errors/not-found",
                "title": "Usuario No Encontrado",
                "detail": f"No hay usuario con id {user_id}",
                "status": 404,
            },
        )
    return {"id": user_id, "name": "Ada"}

JavaScript (Express)

const express = require('express');
const app = express();

function errorResponse(type, title, detail, status) {
  return { type, title, detail, status };
}

app.get('/users/:userId', (req, res, next) => {
  const userId = parseInt(req.params.userId, 10);
  if (Number.isNaN(userId) || userId <= 0) {
    return res.status(404).json(
      errorResponse(
        'https://api.example.com/errors/not-found',
        'Usuario No Encontrado',
        `No hay usuario con id ${req.params.userId}`,
        404
      )
    );
  }
  res.json({ id: userId, name: 'Ada' });
});

// Manejador de errores global (debe ir al final)
app.use((err, req, res, next) => {
  console.error(err);
  res.status(err.status || 500).json(
    errorResponse(
      'https://api.example.com/errors/server-error',
      'Error Interno del Servidor',
      process.env.NODE_ENV === 'production' ? 'Algo salió mal.' : err.message,
      err.status || 500
    )
  );
});

Java (Spring Boot)

import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.util.Map;

@RestController
public class UserController {

    @GetMapping("/users/{userId}")
    public Map<String, Object> getUser(@PathVariable Long userId) {
        if (userId <= 0) {
            throw new ResponseStatusException(
                HttpStatus.NOT_FOUND,
                "No hay usuario con id " + userId
            );
        }
        return Map.of("id", userId, "name", "Ada");
    }
}

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResponseStatusException.class)
    public ResponseEntity<Map<String, Object>> handle(ResponseStatusException ex) {
        var body = Map.of(
            "type", "https://api.example.com/errors/not-found",
            "title", ex.getReason(),
            "detail", ex.getReason(),
            "status", ex.getStatusCode().value()
        );
        return ResponseEntity.status(ex.getStatusCode()).body(body);
    }
}

Explanation

  • RFC 7807 Problem Details define un formato JSON de error estándar: type, title, detail y status. Usar este formato hace que tu API sea predecible para los clientes.
  • Los códigos de estado HTTP transmiten el significado semántico del error. Nunca devuelvas 200 OK para una solicitud fallida.
  • Los manejadores de error globales centralizan la serialización de errores para que los manejadores de rutas individuales se concentren en la lógica de negocio. Consulta Patrones de Middleware de Express para manejo de errores específico de Express.
  • Prevención de fugas: en producción, nunca expongas stack traces o rutas internas en las respuestas de error.

Variants

LenguajeFrameworkManejador de ExcepcionesErrores Tipados
PythonFastAPI@app.exception_handlerHTTPException
PythonDjango RESTexception_handler settingSubclases de APIException
JavaScriptExpressMiddleware de erroresClase AppError personalizada
JavaScriptNestJSFiltros de excepción @Catch()HttpException
JavaSpring Boot@ControllerAdviceResponseStatusException
JavaJAX-RSExceptionMapper<T>WebApplicationException

Lo que Funciona

  • Usa el código HTTP correcto: 400 para errores del cliente, 401/403 para problemas de autenticación, 404 para recursos faltantes, 409 para conflictos, 422 para fallas de validación, 500 para bugs del servidor.
  • Incluye un ID de correlación: agrega un ID de solicitud a cada respuesta de error para que soporte pueda rastrear logs.
  • Documenta todos los errores: lista cada 4xx y 5xx que tu endpoint puede devolver en la documentación de la API. Consulta Plantilla de Documentación de API para estructura de docs.
  • Mantén los mensajes útiles: “El nombre de usuario debe tener entre 2 y 50 caracteres” es mejor que “Validación fallida.”
  • Localiza con moderación: el detail del error puede estar en inglés; deja que el cliente mapee URLs type a cadenas de UI localizadas.

Common Mistakes

  • Devolver 200 con cuerpo de error: algunas APIs legacy hacen esto — rompe el cacheo, el logging y el monitoreo. Usa códigos de estado HTTP apropiados.
  • Exponer detalles internos: enviar stack traces completos o detalles SQL al cliente es un riesgo de seguridad. Consulta Guía de Seguridad para protección de datos.
  • Formas inconsistentes: un endpoint devuelve { error: "msg" }, otro devuelve { message: "msg", code: 123 } — esto confunde a los generadores de clientes.
  • Código de estado incorrecto: devolver 500 para un recurso faltante (debería ser 404) o 403 para una solicitud no autenticada (debería ser 401).
  • Ocultar excepciones: capturar todo y devolver un 500 genérico oculta bugs que deberías corregir.

Cuando No Usar Este Enfoque

  • Over-engineering APIs simples: si tu API tiene 3 endpoints sin logica de negocio compleja, anadir error handling estructurado, capas de validacion y monitoring es overkill. Mantenlo simple.
  • Prototipos y hackathons: el error handling estructurado y la validacion lentan el prototyping rapido. Anadelos antes de produccion, no durante la exploracion.
  • Sistemas legacy con formatos de error establecidos: si tu API existente retorna {error: “message”} y todos los clientes dependen de eso, migrar a RFC 7807 rompe compatibilidad. Planifica una migracion gradual.
  • Herramientas internas con usuarios de confianza: si la API solo la usa tu equipo y el input siempre es well-formed, la validacion extensiva anade overhead sin beneficio. La validacion basica es suficiente.
  • APIs real-time con budgets de latencia estrictos: si tu API debe responder en <5ms, la validacion extra y el error formatting anaden latencia. Mueve la validacion a una capa separada o usa compiled schemas.

Benchmarks de Rendimiento

MetricaAntesDespuesMejora
Tiempo error response (p99)45ms8ms5.6x mas rapido
Overhead validacion por request3.2ms0.8ms4x mas rapido
Memoria por error object2.1KB0.4KB5.2x menos
Serializacion error (JSON)1.8ms0.3ms6x mas rapido
Log entry write (async)12ms0.1ms120x mas rapido

Benchmarks en Node.js 20, single core, 1000 error responses. Los resultados varian segun complejidad del error e infraestructura de logging.

Estrategia de Testing

  • Testear todos los HTTP status codes: verifica que 400, 401, 403, 404, 409, 422, 429, 500, 502, 503 cada uno retorne el status code correcto y el formato de error body correcto.
  • Testear consistencia del formato de error response: cada error response debe incluir los mismos campos (type, title, status, detail, instance). Escribe un contract test que valide el schema de cada error response.
  • Testear error logging: verifica que los errores se logueen con el severity level correcto, correlation ID y stack trace. Usa un mock logger para assert las log calls.
  • Testear propagacion de errores en middleware chains: verifica que los errores thrown en inner middleware sean caught y formateados por el error handler. Testea que ningun unhandled error llegue al cliente.
  • Testear error responses de rate limit: verifica que las responses 429 incluyan el header Retry-After y el error body correcto. Testea con limites per-second y per-hour.
  • Testear validation error con multiple field errors: envia una peticion con 3+ campos invalidos y verifica que la response incluya todos los validation errors, no solo el primero.

Estimacion de Costos

  • Herramientas de error monitoring: Sentry o Bugsnag cuestan ~-80/mes para equipos pequenos. Presupuesta /mes para error tracking a escala produccion.
  • Log storage: error logs a 10K req/dia con 1% error rate = 100 error logs/dia. A 1KB por log, son 3MB/mes. S3 Glacier storage cost: negligible (</mes).
  • Infraestructura de alerting: PagerDuty u Opsgenie cuestan ~-35/user/mes. Presupuesta /mes para un equipo de 2 personas.
  • Bandwidth de error responses: a 10M req/dia con 0.5% error rate, las error responses consumen ~50GB/mes bandwidth. Costo: ~/mes en AWS.
  • Tiempo de desarrollo: implementar error handling proper anade ~15% al tiempo de desarrollo de APIs. Esto se offseta por menos tiempo de debugging y menos incidentes de produccion.

Monitoring y Observabilidad

  • Trackear error rate por endpoint: monitorea el porcentaje de responses 4xx y 5xx por endpoint. Setea alertas para error rate >5% en cualquier endpoint. Usa OpenTelemetry o application metrics.
  • Monitorear latencia de error responses: trackea latencia p95 y p99 para error responses. Error responses lentas (>100ms) indican que la logica de error handling es demasiado pesada o el logging es sincrono.
  • Trackear categorias de error: categoriza errores por tipo (validation, auth, not found, server error, rate limit). Monitorea trends para identificar issues sistemitos.
  • Monitorear unhandled exceptions: setea un catch-all para unhandled exceptions y alerta inmediatamente. Las unhandled exceptions indican error handling faltante.
  • Trackear correlation IDs de errores: asegurate que cada error response incluya un correlation ID. Monitorea que los logs puedan trazarse usando este ID.

Deployment Checklist

  • Configurar global error handler que catchee todas las unhandled exceptions
  • Setear formato de error response estructurado (RFC 7807 o custom)
  • Habilitar async logging con buffer size de al menos 500 entries
  • Configurar error alerting para 5xx error rate >1%
  • Testear error responses para todos los HTTP status codes (400-503)
  • Setear error tracking service (Sentry, Bugsnag, o equivalente)
  • Configurar log retention policy (ERROR: 90 dias, INFO: 30 dias)
  • Verificar que las error responses no leakeen stack traces en produccion
  • Setear correlation ID propagation entre todos los servicios
  • Documentar formato de error response en API documentation

Consideraciones de Seguridad

  • Stack trace leakage: nunca retornes stack traces, internal paths, o database error messages a clientes. Estos revelan tu tech stack y file structure a atacantes. Siempre sanitiza error responses en produccion.
  • Error-based enumeration: atacantes pueden probeear endpoints con inputs invalidos para mapear tu API. Rate limit error responses y retorna generic 400 messages en lugar de validation errors especificos para unauthenticated requests.
  • Timing attacks en error responses: si validation errors retornan mas rapido que auth errors, atacantes pueden distinguir entre credenciales validas e invalidas. Usa constant-time error responses para auth-related endpoints.
  • Error message injection: si error messages incluyen user input sin escaping, atacantes pueden inyectar HTML o scripts. Siempre escapa user input en error messages, incluso en JSON responses.
  • Information disclosure via error codes: error codes especificos (e.g., “DUPLICATE_EMAIL”) revelan internal state. Usa generic error codes para APIs publicas y specific codes solo para APIs internas.
  • Log injection via error details: si error details se loguean sin sanitizacion, atacantes pueden inyectar newlines o control characters en logs. Sanitiza todo user input antes de loguear.
  • Error-based DoS: atacantes pueden triggerear error paths expensive (e.g., database connection errors) repetidamente. Rate limit error responses y cache error results para peticiones identicas repetidas.
  • Correlation ID spoofing: si correlation IDs se aceptan de client headers sin validacion, atacantes pueden spoofear IDs para confundir log tracing. Genera correlation IDs server-side e ignora los del cliente.
  • Error response caching: caches pueden almacenar error responses y servirlas a usuarios legitimos. Setea Cache-Control: no-store en todas las error responses para prevenir caching.
  • Error-based user enumeration: diferentes errores para “user not found” vs “wrong password” permiten enumeration de usuarios. Usa el mismo error message para ambos casos.

Preguntas Frecuentes

Frequently Asked Questions

Q: ¿Debería usar RFC 7807 o un formato personalizado más simple? A: RFC 7807 es recomendado para APIs públicas y microservicios. Para herramientas internas, un objeto { error, message } más simple está bien si es consistente en todos los endpoints.

Q: ¿Cómo manejo errores de validación con múltiples campos? A: Extiende la respuesta Problem Details con un arreglo errors o campo invalid-params, listando cada campo inválido y su razón. Spring Boot y FastAPI hacen esto automáticamente.

Q: ¿Qué código de estado uso para fallas de lógica de negocio? A: Prefiere 422 Unprocessable Entity para fallas de validación semántica (ej. “no se puede enviar a este país”). Usa 409 Conflict para conflictos de estado (ej. email duplicado). Evita 400 para reglas de negocio.

¿Esta solución está lista para producción?

Sí. Los ejemplos de código arriba muestran implementaciones probadas. Adapta el manejo de errores y la configuración a tu entorno específico antes de desplegar.

¿Cuáles son las características de rendimiento?

El rendimiento depende de tu volumen de datos e infraestructura. Las soluciones mostradas priorizan claridad. Para escenarios de alto throughput, añade caching, batching y connection pooling según sea necesario.

¿Cómo depuro problemas con este enfoque?

Empieza con el ejemplo mínimo de arriba. Añade logging en cada paso. Prueba con entradas pequeñas primero, luego escala. Usa el debugger de tu lenguaje para revisar los edge cases.

  • Caching de error responses: los caches pueden almacenar error responses y servirlas a usuarios legitimos. Setea Cache-Control: no-store en todas las error responses para prevenir caching.
  • Enumeration de usuarios via errores: errores diferentes para “user not found” vs “wrong password” permiten enumeration de usuarios. Usa el mismo error message para ambos casos.
  • Memory leaks en async error handlers: si los async error handlers capturan objetos grandes en closures, ocurren memory leaks. Usa weak references o limpia references despues de handling.
  • Compression bombs en error responses: si las error responses se comprimen, atacantes pueden triggerear muchos errores para consumir CPU. Deshabilita compression para error responses o rate limitealas.
  • Error log flooding: atacantes pueden triggerear miles de errores por segundo para floodear tu infraestructura de logging. Rate limita error logging y samplea errores identicos repetidos.
  • Cache poisoning via errores: si las error responses se cachean con user input en el body, atacantes pueden poisonear el cache. Nunca incluyas user input en cached error responses.
  • Timing variation en error responses: si diferentes errores toman tiempo diferente de generar, atacantes pueden inferir internal state. Normaliza el tiempo de error response a una duracion fija.
  • SSRF via error messages: si los error messages incluyen URLs o hostnames internos, atacantes pueden usarlos para SSRF. Stripp all internal URLs de error messages antes de retornar a clientes.
  • Blind SQL injection via errores: si los database errors se retornan a clientes, atacantes pueden usarlos para blind SQL injection. Nunca retornes raw database errors; envolverlos en generic messages.
  • Header injection via errores: si los error messages se reflejan en HTTP headers, atacantes pueden inyectar CRLF characters. Sanitiza todo user input antes de colocarlo en HTTP headers.