Definición
Los códigos de estado 4xx de Error del Cliente indican que la solicitud enviada por el cliente contiene un error o no puede cumplirse. El error está del lado del cliente (sintaxis incorrecta, datos inválidos, autenticación faltante, etc.).
Los códigos de estado 4xx más comunes son:
- 400 Bad Request - Sintaxis de solicitud malformada o datos inválidos
- 401 Unauthorized - Credenciales de autenticación faltantes o inválidas
- 403 Forbidden - Autenticado pero carece de permiso para acceder al recurso
- 404 Not Found - El recurso no existe en la URI especificada
- 409 Conflict - La solicitud entra en conflicto con el estado actual (ej. email duplicado)
- 422 Unprocessable Entity - Errores de validación en los datos de la solicitud
- 429 Too Many Requests - Límite de tasa excedido
Los códigos 4xx están definidos en RFC 7231 y RFC 6585, indicando que el cliente debe modificar la solicitud antes de reintentar.
Ejemplo
400 Bad Request - JSON Inválido:
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"name": "Alice",
"email": "not-an-email" // Formato de email inválido
}
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "Validación fallida",
"details": [
{
"field": "email",
"message": "Formato de email inválido"
}
]
}
401 Unauthorized - Token Faltante:
GET /api/users/me HTTP/1.1
Host: api.example.com
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api.example.com"
Content-Type: application/json
{
"error": "Autenticación requerida",
"message": "Token de acceso faltante o inválido"
}
403 Forbidden - Permisos Insuficientes:
DELETE /api/users/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer YOUR_TOKEN
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": "Prohibido",
"message": "No tienes permiso para eliminar este usuario"
}
404 Not Found - El Recurso No Existe:
GET /api/users/999 HTTP/1.1
Host: api.example.com
HTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": "No encontrado",
"message": "El usuario con ID 999 no existe"
}
409 Conflict - Recurso Duplicado:
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"name": "Alice",
"email": "[email protected]" // El email ya existe
}
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": "Conflicto",
"message": "Ya existe un usuario con el email [email protected]"
}
422 Unprocessable Entity - Error de Validación:
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"name": "A", // Muy corto
"email": "[email protected]",
"age": -5 // Edad inválida
}
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": "Validación fallida",
"details": [
{
"field": "name",
"message": "El nombre debe tener al menos 2 caracteres"
},
{
"field": "age",
"message": "La edad debe ser un número positivo"
}
]
}
Ejemplo de Código
JavaScript (Fetch API):
const createUser = async (userData) => {
try {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN'
},
body: JSON.stringify(userData)
});
// Manejar errores 4xx específicos
if (response.status === 400) {
const error = await response.json();
console.error('Bad Request:', error);
throw new Error(`Validación fallida: ${JSON.stringify(error.details)}`);
}
if (response.status === 401) {
console.error('No autorizado - token expirado o faltante');
// Redirigir al login
window.location.href = '/login';
return;
}
if (response.status === 403) {
const error = await response.json();
console.error('Prohibido:', error.message);
throw new Error('Permisos insuficientes');
}
if (response.status === 404) {
console.error('Recurso no encontrado');
throw new Error('Usuario no encontrado');
}
if (response.status === 409) {
const error = await response.json();
console.error('Conflicto:', error.message);
throw new Error('El usuario ya existe');
}
if (response.status === 422) {
const error = await response.json();
console.error('Error de Validación:', error.details);
throw new Error('Datos de usuario inválidos');
}
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
console.error('Límite de tasa excedido. Reintentar después de:', retryAfter);
throw new Error(`Demasiadas solicitudes. Reintentar después de ${retryAfter} segundos`);
}
// Verificar si la respuesta es exitosa (2xx)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('Error creando usuario:', error);
throw error;
}
};
// Uso con manejo de errores
const newUser = {
name: 'Alice Smith',
email: '[email protected]'
};
createUser(newUser)
.then(user => console.log('Creado:', user))
.catch(error => {
// Mostrar mensaje de error amigable
alert(`Fallo al crear usuario: ${error.message}`);
});
Python (librería requests):
import requests
import time
def create_user(user_data):
try:
response = requests.post(
'https://api.example.com/users',
json=user_data,
headers={'Authorization': 'Bearer YOUR_TOKEN'}
)
# Manejar errores 4xx específicos
if response.status_code == 400:
error = response.json()
print(f'Bad Request: {error}')
raise ValueError(f"Validación fallida: {error.get('details')}")
if response.status_code == 401:
print('No autorizado - token expirado o faltante')
# Redirigir al login o refrescar token
raise PermissionError('Autenticación requerida')
if response.status_code == 403:
error = response.json()
print(f"Prohibido: {error.get('message')}")
raise PermissionError('Permisos insuficientes')
if response.status_code == 404:
print('Recurso no encontrado')
raise FileNotFoundError('Usuario no encontrado')
if response.status_code == 409:
error = response.json()
print(f"Conflicto: {error.get('message')}")
raise ValueError('El usuario ya existe')
if response.status_code == 422:
error = response.json()
print(f"Error de Validación: {error.get('details')}")
raise ValueError('Datos de usuario inválidos')
if response.status_code == 429:
retry_after = response.headers.get('Retry-After')
print(f'Límite de tasa excedido. Reintentar después de: {retry_after}')
# Esperar y reintentar
if retry_after:
time.sleep(int(retry_after))
return create_user(user_data) # Reintentar
raise Exception('Demasiadas solicitudes')
# Verificar si la respuesta es exitosa (2xx)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f'Error creando usuario: {e}')
raise
# Uso con manejo de errores
new_user = {
'name': 'Alice Smith',
'email': '[email protected]'
}
try:
user = create_user(new_user)
print(f'Creado: {user}')
except Exception as error:
print(f'Fallo al crear usuario: {error}')
Diagrama
graph TB
subgraph "Errores 4xx del Cliente"
E400[400 Bad Request
Sintaxis/datos inválidos]
E401[401 Unauthorized
Autenticación faltante]
E403[403 Forbidden
Sin permiso]
E404[404 Not Found
Recurso faltante]
E409[409 Conflict
Duplicado/conflicto de estado]
E422[422 Unprocessable
Validación fallida]
E429[429 Too Many
Límite de tasa]
end
subgraph "Acciones del Cliente"
A1[Corregir sintaxis de solicitud]
A2[Agregar/refrescar token]
A3[Solicitar acceso]
A4[Verificar URI]
A5[Modificar datos]
A6[Corregir validación]
A7[Esperar y reintentar]
end
E400 --> A1
E401 --> A2
E403 --> A3
E404 --> A4
E409 --> A5
E422 --> A6
E429 --> A7
Analogía
Piensa en los errores 4xx como problemas en la entrada de un restaurante:
- 400 Bad Request → “Tu formulario de pedido está incompleto”
- 401 Unauthorized → “Necesitas mostrar ID para entrar”
- 403 Forbidden → “Esta es la sección VIP, no puedes entrar”
- 404 Not Found → “Ese plato no está en nuestro menú”
- 409 Conflict → “Ya tienes una reserva”
- 429 Too Many Requests → “Estás ordenando demasiado rápido, ve más despacio”
Buenas Prácticas
- Usar Códigos 4xx Específicos - No usar 400 para todo; usar 401/403/404/422 apropiadamente
- Incluir Detalles del Error - Proporcionar mensajes útiles explicando qué salió mal
- Validar Temprano - Devolver 400/422 antes de procesar para evitar recursos desperdiciados
- Devolver Errores a Nivel de Campo - Incluir errores de validación específicos para cada campo
- Usar WWW-Authenticate - Incluir encabezado con 401 para indicar método de autenticación
- Establecer Retry-After - Incluir encabezado con 429 para indicar cuándo reintentar
- Registrar Errores 4xx - Rastrear errores del cliente para identificar problemas de usabilidad de la API
Errores Comunes
- 400 para Todo - Usar 400 Bad Request para todos los errores en lugar de códigos específicos
- Confusión 401 vs 403 - Mezclar autenticación (401) vs autorización (403)
- Sin Detalles del Error - Devolver errores genéricos sin mensajes útiles
- Exponer Info Interna - Incluir stack traces o errores de base de datos en respuestas 4xx
- Sin Retry-After - No indicar a los clientes cuándo reintentar después de limitación de tasa
- Formato Inconsistente - Cambiar estructura de respuesta de error entre endpoints
- 404 para Fallos de Auth - Devolver 404 para ocultar existencia de recursos (seguridad por oscuridad)