Una aplicación que consulta una API con frecuencia puede descargar la misma respuesta muchas veces, aunque los datos no hayan cambiado. Esto aumenta la transferencia de datos y puede hacer que el cliente dedique tiempo innecesario a recibir y procesar contenido repetido.
La caché HTTP con ETag ofrece una forma de evitar parte de ese trabajo. En lugar de enviar siempre el recurso completo, el servidor proporciona un identificador de la representación y el cliente puede utilizarlo para preguntar si sigue vigente. Si no hay cambios, la respuesta puede llegar sin cuerpo.
En este artículo aprenderás cómo funciona ese intercambio, cómo configurar sus cabeceras y qué precauciones tomar al aplicarlo en una API.
Qué es un ETag y qué problema resuelve
Un ETag es un validador que el servidor incluye en la cabecera ETag de una respuesta HTTP. Su valor identifica una versión de la representación entregada: por ejemplo, el JSON de un producto con un formato y una codificación determinados.
El cliente guarda ese valor junto con la respuesta. Cuando necesita comprobar si todavía puede reutilizarla, envía el ETag en la cabecera If-None-Match. El servidor lo compara con el validador actual y decide qué responder:
- Si la representación no ha cambiado, puede devolver
304 Not Modified, sin cuerpo de respuesta. - Si ha cambiado, devuelve normalmente
200 OK, con el contenido actualizado y su ETag correspondiente.
El beneficio principal es reducir la transferencia del cuerpo cuando el contenido permanece igual. La comprobación sigue siendo una solicitud HTTP: hay cabeceras, una conexión y procesamiento en ambos extremos. Por eso, ETag no elimina por sí solo la latencia ni garantiza una mejora en todos los escenarios.
Cómo funciona una petición condicional
Imagina una API que devuelve los datos de un producto. En la primera consulta, el cliente todavía no tiene una copia guardada:
GET /productos/42 HTTP/1.1
Host: api.ejemplo.test
Accept: application/json
El servidor responde con el recurso y un ETag:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, no-cache
ETag: "producto-42-v7"
{"id":42,"nombre":"Teclado","disponible":true}
La directiva private indica que una caché compartida no debe almacenar la respuesta. no-cache permite almacenarla, pero exige validarla antes de reutilizarla. Es una combinación útil cuando el contenido puede guardarse en el cliente, pero conviene confirmar su vigencia en cada reutilización.
Más adelante, el cliente envía el valor recibido:
GET /productos/42 HTTP/1.1
Host: api.ejemplo.test
Accept: application/json
If-None-Match: "producto-42-v7"
Si la representación sigue siendo la misma, el servidor responde:
HTTP/1.1 304 Not Modified
Cache-Control: private, no-cache
ETag: "producto-42-v7"
La respuesta 304 no contiene el JSON: el cliente reutiliza la copia que ya tiene. Si el producto cambia, el servidor responde con 200 OK, el nuevo cuerpo y un ETag actualizado. Una respuesta 304 también debe incluir, cuando corresponda, otras cabeceras de metadatos que habrían acompañado a la respuesta equivalente, como Vary.
Configura la política de caché antes de añadir ETag
ETag es un validador; Cache-Control determina, entre otras cosas, si una respuesta se puede almacenar y durante cuánto tiempo se considera vigente. Conviene diseñar ambas cosas juntas.
Vigencia y revalidación
Si un recurso puede reutilizarse durante un tiempo sin contactar con el servidor, puedes definir una duración con max-age. Por ejemplo:
Cache-Control: private, max-age=60
ETag: "producto-42-v7"
Durante ese periodo, una caché privada puede reutilizar la respuesta sin realizar una nueva petición. Una vez transcurrido, puede revalidarla con el ETag. Este enfoque reduce solicitudes, pero permite que el cliente use datos que pueden haber cambiado durante la ventana de vigencia.
Si necesitas comprobar antes de cada reutilización, utiliza no-cache, como en el ejemplo anterior. No lo confundas con no-store: esta última directiva indica que la respuesta no debe almacenarse. Para información que no debería quedar guardada en caché, no-store suele ser más adecuada que una estrategia basada en revalidación.
Cachés privadas y compartidas
Una caché privada pertenece a un cliente concreto; una compartida puede atender a varios usuarios, como ocurre con algunos proxies o CDN. Si la respuesta contiene datos personalizados, usa una política apropiada para impedir que una caché compartida la sirva a otra persona.
private es una opción habitual para respuestas que pueden almacenarse localmente. Para datos especialmente sensibles, considera no-store. No conviertas una respuesta autenticada en pública sin revisar las reglas de caché y el comportamiento de toda la infraestructura.
ETag fuerte y débil: cuál elegir
Hay dos formas habituales de presentar el validador:
ETag: "version-7"
ETag: W/"version-7"
El primer valor es fuerte: identifica una representación con precisión a nivel de bytes. Si los bytes de la representación cambian, el validador fuerte debe cambiar. El segundo es débil, indicado por el prefijo W/: puede identificar representaciones equivalentes para el propósito del servidor aunque sus bytes no sean idénticos.
Para revalidar solicitudes GET con If-None-Match, HTTP utiliza comparación débil. Eso permite comprobar si el contenido sigue siendo equivalente para reutilización, incluso si el validador está marcado como débil.
La elección importa en otras operaciones. Por ejemplo, If-Match, usado para evitar sobrescrituras, emplea comparación fuerte. No conviene asumir que un ETag débil sirve para todos los mecanismos de concurrencia o para todas las solicitudes de rango.
Cómo implementar ETag en una API
La implementación concreta depende del framework y de dónde se produzca la respuesta. Estos pasos ayudan a diseñarla sin vincularla a un lenguaje específico:
- Identifica la representación. Determina qué recurso se devuelve y qué factores modifican su contenido, como formato, idioma o permisos.
- Genera un validador. Puede derivarse de una versión del recurso o de un hash del contenido. Una versión resulta práctica si cambia siempre que cambia la representación; un hash exige calcularse sobre los datos pertinentes.
- Configura la política de caché. Decide si el recurso puede almacenarse, si debe ser privado y cuándo debe revalidarse.
- Evalúa
If-None-Match. Si la condición coincide en una peticiónGEToHEAD, responde según las reglas HTTP, normalmente con304y sin cuerpo. - Entrega el contenido cuando corresponda. Si no coincide, responde con la representación actual y su ETag.
- Prueba el recorrido completo. Verifica tanto el caso sin cambios como la actualización del recurso y las distintas variantes de respuesta.
Utiliza las funciones de negociación condicional de tu framework o una biblioteca HTTP bien probada en lugar de comparar cadenas de manera improvisada. If-None-Match puede contener varios validadores o el comodín *, y las reglas de comparación y precedencia de condiciones requieren atención.
Ten en cuenta las variantes
Una misma URL puede producir representaciones distintas según cabeceras como Accept, Accept-Language o Accept-Encoding. Si una cabecera influye en la selección, Vary ayuda a las cachés a distinguir las respuestas correspondientes:
Vary: Accept-Language, Accept-Encoding
Además, un ETag fuerte debe ser coherente con los bytes de cada variante. Esto es importante cuando una capa posterior comprime o transforma la respuesta. Revisa cómo interactúan la aplicación, el proxy y la CDN para que no asignen un mismo validador fuerte a representaciones que difieren en bytes.
Usa ETag también para evitar sobrescrituras
Además de ahorrar transferencia en lecturas, los validadores pueden ayudar a controlar actualizaciones concurrentes. Supongamos que dos personas consultan el mismo recurso y una lo modifica antes que la otra. La segunda puede enviar el ETag que recibió mediante If-Match al actualizar:
PUT /productos/42 HTTP/1.1
Host: api.ejemplo.test
If-Match: "producto-42-v7"
Content-Type: application/json
{"id":42,"nombre":"Teclado compacto","disponible":true}
Si la versión actual ya no coincide, el servidor puede responder 412 Precondition Failed en lugar de sobrescribir silenciosamente los cambios. El cliente tendrá que recuperar el estado actual y resolver el conflicto. Esta técnica requiere que el servidor evalúe la condición junto con la actualización de forma segura y que el validador fuerte represente adecuadamente el estado protegido.
Cómo comprobar que realmente ahorras datos
Una prueba básica puede hacerse con cualquier cliente HTTP que permita inspeccionar cabeceras. Primero solicita el recurso y guarda su ETag. Después repite la petición con If-None-Match: si nada ha cambiado y la API admite revalidación, comprueba que recibes 304 sin cuerpo. Por último, modifica el recurso y confirma que la siguiente consulta devuelve el contenido nuevo con otro validador.
También conviene revisar:
- Bytes transferidos, no solo el número de respuestas
304, porque las cabeceras siguen ocupando espacio. - Coste de generar el ETag: calcular un hash después de construir una respuesta grande puede ahorrar red, pero no necesariamente procesamiento.
- Frecuencia de cambios: si el recurso cambia en casi todas las consultas, habrá menos oportunidades de reutilización.
- Comportamiento del cliente: una biblioteca o el navegador pueden gestionar la caché automáticamente, mientras que un cliente propio quizá necesite almacenar tanto el cuerpo como el validador.
- Respuesta sin copia local: un cliente no debe depender de un
304si ya no conserva el cuerpo asociado; necesita obtener una representación completa.
En las herramientas del navegador, la presentación de las solicitudes y de las respuestas en caché puede variar. Inspecciona tanto las cabeceras como el comportamiento efectivo del cliente antes de sacar conclusiones.
Conclusión
La caché HTTP con ETag permite revalidar una representación y evitar el envío repetido de su cuerpo cuando no ha cambiado. Para aprovecharla, combina validadores coherentes con una política Cache-Control adecuada, distingue las variantes y protege los datos personalizados.
Empieza por un endpoint de lectura que se consulte con frecuencia y cambie poco. Prueba los casos de coincidencia y actualización, y mide la transferencia y el coste de procesamiento antes de extender la estrategia al resto de la API.

