404 Not Found vs 410 Gone

Fundamentals Jan 9, 2026 HTTP
http status-codes rest client-errors

Definición

404 Not Found y 410 Gone son ambos códigos de error del cliente HTTP que indican que un recurso no está disponible, pero transmiten diferentes significados semánticos sobre el estado del recurso.

404 Not Found:

  • Significado: El recurso solicitado no pudo encontrarse en el servidor
  • Razón: El recurso nunca existió, URI incorrecta, o movido sin redirección
  • Permanencia: El estado puede ser temporal o permanente (ambiguo)
  • Caché: Los clientes pueden reintentar más tarde (podría ser un problema temporal)
  • Caso de Uso: El recurso no existe, URL incorrecta, o elementos eliminados de forma suave

410 Gone:

  • Significado: El recurso existió previamente pero ha sido eliminado permanentemente
  • Razón: Eliminación permanente intencional (ej. contenido removido, usuario eliminó cuenta)
  • Permanencia: Explícitamente permanente (el recurso NUNCA volverá)
  • Caché: Los clientes no deben reintentar y pueden purgar referencias en caché
  • Caso de Uso: Eliminación de contenido, promociones expiradas, cuentas de usuario eliminadas

Distinción Clave:

  • 404 → “No tengo ese recurso” (ambiguo - podría volver)
  • 410 → “Lo tenía, pero se fue para siempre” (definitivo)

Ejemplo

404 Not Found - El Recurso Nunca Existió:

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"
}

404 Not Found - URL Incorrecta:

GET /api/usres/123 HTTP/1.1
Host: api.example.com

HTTP/1.1 404 Not Found
Content-Type: application/json

{
  "error": "No encontrado",
  "message": "El endpoint solicitado no existe. ¿Quisiste decir /api/users/123?"
}

410 Gone - Eliminado Permanentemente:

GET /api/users/123 HTTP/1.1
Host: api.example.com

HTTP/1.1 410 Gone
Content-Type: application/json

{
  "error": "Eliminado",
  "message": "Esta cuenta de usuario fue eliminada permanentemente el 2026-01-05",
  "deletedAt": "2026-01-05T10:30:00Z",
  "reason": "El usuario solicitó la eliminación de la cuenta"
}

410 Gone - Contenido Expirado:

GET /api/promotions/summer-sale-2025 HTTP/1.1
Host: api.example.com

HTTP/1.1 410 Gone
Content-Type: application/json

{
  "error": "Eliminado",
  "message": "Esta promoción ha expirado y ya no está disponible",
  "expiredAt": "2025-09-01T00:00:00Z",
  "alternative": "/api/promotions/current"
}

Ejemplo de Código

JavaScript (Fetch API):

const fetchUser = async (userId) => {
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`, {
      headers: {
        'Accept': 'application/json',
        'Authorization': 'Bearer YOUR_TOKEN'
      }
    });

    // Manejar 404 Not Found
    if (response.status === 404) {
      const error = await response.json();
      console.warn('404 No encontrado:', error.message);

      // Podría reintentar más tarde o verificar si la URL es correcta
      // El recurso podría existir en una ubicación diferente
      throw new Error(`Usuario ${userId} no encontrado`);
    }

    // Manejar 410 Gone
    if (response.status === 410) {
      const error = await response.json();
      console.error('410 Eliminado:', error.message);

      // Recurso eliminado permanentemente - no reintentar
      // Eliminar de caché, borrar marcadores, etc.
      removeFromCache(userId);

      throw new Error(`Usuario ${userId} fue eliminado permanentemente: ${error.message}`);
    }

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    return await response.json();

  } catch (error) {
    console.error('Error obteniendo usuario:', error);
    throw error;
  }
};

const removeFromCache = (userId) => {
  // Eliminar de caché local
  localStorage.removeItem(`user-${userId}`);

  // Eliminar de marcadores de UI
  console.log(`Usuario ${userId} eliminado de caché`);
};

// Ejemplo: Manejo con diferente feedback de UI
const displayUser = async (userId) => {
  try {
    const user = await fetchUser(userId);
    console.log('Usuario:', user);

  } catch (error) {
    if (error.message.includes('no encontrado')) {
      // 404 - podría ser temporal
      alert('Usuario no encontrado. Por favor, verifica el ID del usuario e intenta de nuevo.');

    } else if (error.message.includes('eliminado permanentemente')) {
      // 410 - permanente, no ofrecer reintento
      alert('Esta cuenta de usuario ha sido eliminada permanentemente y no puede accederse.');
      window.location.href = '/users'; // Redirigir a lista de usuarios
    }
  }
};

Python (librería requests):

import requests

def fetch_user(user_id):
    try:
        response = requests.get(
            f'https://api.example.com/users/{user_id}',
            headers={
                'Accept': 'application/json',
                'Authorization': 'Bearer YOUR_TOKEN'
            }
        )

        # Manejar 404 Not Found
        if response.status_code == 404:
            error = response.json()
            print(f'404 No encontrado: {error.get("message")}')

            # Podría reintentar más tarde o verificar si la URL es correcta
            # El recurso podría existir en una ubicación diferente
            raise FileNotFoundError(f'Usuario {user_id} no encontrado')

        # Manejar 410 Gone
        if response.status_code == 410:
            error = response.json()
            print(f'410 Eliminado: {error.get("message")}')

            # Recurso eliminado permanentemente - no reintentar
            # Eliminar de caché, borrar marcadores, etc.
            remove_from_cache(user_id)

            raise Exception(f'Usuario {user_id} fue eliminado permanentemente: {error.get("message")}')

        response.raise_for_status()

        return response.json()

    except requests.exceptions.RequestException as e:
        print(f'Error obteniendo usuario: {e}')
        raise

def remove_from_cache(user_id):
    # Eliminar de caché/base de datos
    print(f'Usuario {user_id} eliminado de caché')

# Ejemplo: Manejo con diferente feedback de UI
def display_user(user_id):
    try:
        user = fetch_user(user_id)
        print(f'Usuario: {user}')

    except FileNotFoundError as e:
        # 404 - podría ser temporal
        print(f'Usuario no encontrado: {e}')
        print('Por favor, verifica el ID del usuario e intenta de nuevo.')

    except Exception as e:
        # 410 - permanente, no ofrecer reintento
        if 'eliminado permanentemente' in str(e):
            print(f'Usuario eliminado permanentemente: {e}')
            print('Esta cuenta de usuario no puede accederse.')

Diagrama

flowchart TB
    START[GET /api/resource/123] --> EXISTS{¿Recurso
Existe?} EXISTS -->|No| EVER_EXISTED{¿Alguna vez
Existió?} EXISTS -->|Sí| RETURN_200[Devolver 200 OK
con datos del recurso] EVER_EXISTED -->|Nunca| RETURN_404[Devolver 404 Not Found
Recurso desconocido] EVER_EXISTED -->|Sí, Eliminado| PERMANENT{¿Eliminación
Permanente?} PERMANENT -->|Sí| RETURN_410[Devolver 410 Gone
Eliminado permanentemente] PERMANENT -->|No| RETURN_404_SOFT[Devolver 404 Not Found
Eliminado suave/oculto] RETURN_404 --> CLIENT_404[Cliente: Verificar URL
Puede reintentar más tarde] RETURN_404_SOFT --> CLIENT_404_SOFT[Cliente: Verificar permisos
Puede reintentar] RETURN_410 --> CLIENT_410[Cliente: Purgar caché
Nunca reintentar] RETURN_200 --> CLIENT_200[Cliente: Usar recurso] style RETURN_404 fill:#ffa726 style RETURN_404_SOFT fill:#ffa726 style RETURN_410 fill:#ef5350 style RETURN_200 fill:#66bb6a

Analogía

Piensa en 404 vs 410 como buscar un libro en una biblioteca:

  • 404 Not Found → “No tenemos ese libro” (quizás nunca lo tuvimos, o está prestado, o extraviado)
  • 410 Gone → “Solíamos tenerlo, pero lo eliminamos permanentemente de la colección” (descartado, destruido o dado de baja)

Con 404, podrías volver a consultar más tarde. Con 410, no te molestes - nunca volverá.

Buenas Prácticas

  1. Usar 410 para Eliminaciones Intencionales - Cuando deliberadamente eliminas contenido permanentemente
  2. Usar 404 para Recursos Desconocidos - Cuando el recurso nunca existió o ubicación desconocida
  3. Incluir Timestamp de Eliminación - Agregar campo deletedAt en respuestas 410
  4. Sugerir Alternativas - Proporcionar enlaces a recursos similares en respuestas 410
  5. Cachear Respuestas 410 - Los clientes pueden cachear 410 para evitar solicitudes repetidas
  6. Registrar Razones de Eliminación - Rastrear por qué los recursos fueron eliminados permanentemente
  7. Eliminación Suave vs Dura - Usar 404 para eliminaciones suaves, 410 para eliminaciones duras

Errores Comunes

  • Usar 404 para Todo - No usar 410 cuando los recursos son eliminados permanentemente
  • Sin Info de Eliminación - No explicar por qué el recurso se fue en respuestas 410
  • Semántica Inconsistente - Usar 410 para indisponibilidad temporal (debería ser 503)
  • Sin Timestamp - No incluir cuándo fue eliminado el recurso en respuestas 410
  • Reintentar 410 - Clientes reintentando respuestas 410 (deberían purgar de caché)
  • 404 para Eliminaciones Suaves - Confundir eliminaciones suaves con eliminaciones permanentes
  • Sin Encabezados de Caché - No establecer caché apropiada para respuestas 404/410

Estándares y RFCs

Términos Relacionados