Regresar
601

Versionado de APIs: URL, header o ninguno

Actualizado: 15/09/2026

Versionar una API es una de esas tareas que no duelen el día que se hacen y duelen mucho el día que no se hicieron. Mientras la consumes solo tú, cambiar un campo es editar dos archivos. En cuanto la consume alguien más, ese mismo cambio es una llamada de teléfono.

La pregunta previa, entonces, no es qué estrategia usar sino si hace falta ya. Y la respuesta depende de una sola cosa: si hay código que no controlas llamando a tu API.

Qué se rompe exactamente

Conviene precisar qué es un cambio que rompe, porque la intuición falla en ambos sentidos.

Añadir un campo nuevo a una respuesta es seguro: los clientes que no lo conocen lo ignoran. Añadir un parámetro opcional, también. Añadir un endpoint nuevo, ni se nota.

Lo que rompe es quitar o cambiar: eliminar un campo, renombrarlo, cambiar su tipo, hacer obligatorio un parámetro que no lo era, alterar el significado de un valor o cambiar el formato de las fechas. También rompe, y esto se olvida más, cambiar un código de estado o la estructura de los errores.

Hay un caso intermedio que causa incidentes silenciosos: reducir lo que se devuelve por rendimiento. Quitar de un listado los campos que casi nadie usaba es un cambio que rompe para quien sí los usaba, y no aparece en ninguna prueba propia.

Tres formas de versionar, y lo que cuesta cada una

Las opciones establecidas son tres, y la tercera es no versionar, que también es una decisión legítima.

Tres estrategias comparadas: en la ruta, con un ejemplo de barra api barra v2 barra pedidos, que se ve de un vistazo pero duplica direcciones; en una cabecera, con Accept y un tipo propio, que mantiene la dirección estable pero es invisible al depurar; y sin versión, admitiendo solo cambios compatibles, que no obliga a mantener nada por duplicado pero exige disciplina
La pregunta previa: ¿alguien externo consume esta API? Si no, versionar puede esperar.

La diferencia práctica entre las dos primeras es de visibilidad. Con la versión en la ruta, cualquiera ve qué está llamando con solo mirar la petición, y se puede probar pegando la dirección en el navegador. Con la versión en una cabecera, la dirección del recurso se mantiene estable, que es lo que defienden los puristas del protocolo, a costa de que depurar exija recordar enviarla.

Para un proyecto pequeño, la versión en la ruta gana casi siempre por una razón poco elegante y muy real: cuando algo falla, se diagnostica con las herramientas que ya tienes y sin preparar nada.

La opción de no versionar, que no es pereza

Hay una escuela que sostiene que una API bien diseñada no necesita versiones, y tiene argumentos sólidos.

La idea es comprometerse a que los cambios sean siempre compatibles: solo se añade, nunca se quita ni se renombra. Un campo que deja de tener sentido se marca como obsoleto, se deja de documentar y se sigue devolviendo, quizá vacío, durante años.

La ventaja es que desaparece el trabajo de mantener dos versiones vivas, que es considerable: código duplicado, pruebas duplicadas y una decisión permanente sobre cuándo apagar la antigua.

El costo es disciplina sostenida en el tiempo, y ahí está el problema. Basta con una persona con prisa, una tarde, para que un renombrado se cuele. Por eso esta estrategia funciona bien cuando hay revisión de cambios y mal cuando cada uno publica lo suyo.

Cuántas versiones mantener vivas

La decisión que más trabajo genera no es cómo versionar sino cuántas versiones sostener a la vez, y conviene fijarla antes de crear la segunda.

La respuesta razonable para un equipo pequeño es dos: la actual y la anterior. Con tres, el mantenimiento se multiplica y las correcciones hay que aplicarlas en todas.

Eso obliga a tener un plan de retirada desde el principio. Anunciar la fecha, avisar a quien la usa, y apagarla cuando llegue. Una versión sin fecha de caducidad no se apaga nunca, y acaba siendo código que nadie entiende pero nadie se atreve a tocar.

Conviene además avisar por la propia API, no solo por correo. Una cabecera que indique que esa versión está obsoleta y cuándo dejará de funcionar llega a quien de verdad la está llamando, que no siempre es quien recibió el correo.

Saber quién usa qué antes de apagar nada

La retirada se decide mucho mejor con datos que con suposiciones, y el dato ya está en tus registros.

# Qué versiones se llaman de verdad, ordenadas por uso
awk '{print $7}' /var/log/nginx/access.log \
  | grep -oE '/api/v[0-9]+' | sort | uniq -c | sort -rn

# Quién sigue llamando a la versión antigua, y cuándo fue la última vez
grep '/api/v1/' /var/log/nginx/access.log \
  | awk '{print $1}' | sort | uniq -c | sort -rn | head

# Última llamada registrada a v1
grep '/api/v1/' /var/log/nginx/access.log | tail -1

Si en un mes no hay ninguna llamada, se apaga sin más. Si hay tres direcciones conocidas, se avisa antes. Esa diferencia es la que separa una retirada ordenada de una llamada de un cliente enfadado.

Para que esto funcione conviene registrar también qué cliente llama, con una cabecera que identifique cada integración. Sin ella los registros dicen qué se llama pero no quién, que es justo la mitad que hace falta.

Versionar el contenido, no solo la dirección

Hay un detalle que se pasa por alto y que evita bastantes roturas: no toda la API tiene que cambiar de versión a la vez.

Si el cambio afecta a un recurso, se puede versionar ese recurso y dejar el resto igual. Obligar a los clientes a migrar todo porque cambió un endpoint es lo que hace que las migraciones se pospongan indefinidamente.

La forma más simple de conseguirlo, con la versión en la ruta, es que la versión antigua siga existiendo y redirija internamente a la nueva implementación para todo lo que no cambió. Así solo se mantiene código duplicado en lo que de verdad difiere.

Eso mantiene la promesa de estabilidad sin que el costo crezca con el tamaño de la API, que es el motivo real por el que muchos equipos acaban con una sola versión congelada y miedo a tocarla.

Lo que hay que versionar y casi nadie versiona

La conversación se centra en los endpoints, y hay tres cosas más que rompen a los clientes igual.

  • Los webhooks que envías. Si cambias el cuerpo de un evento, rompes el endpoint de quien lo recibe, y ahí no hay forma de que elija versión. Conviene incluir un campo de versión en el propio evento desde el primer día.
  • Los errores. Su estructura es parte del contrato: si un cliente distingue casos por un código, cambiarlo rompe su lógica aunque el endpoint siga igual.
  • Los valores de campos cerrados. Añadir un estado nuevo a una lista de estados posibles puede romper a quien los trataba de forma exhaustiva.

Ese último merece una nota en la documentación: advertir que pueden aparecer valores nuevos hace que los clientes se escriban tolerantes desde el principio, y evita la discusión sobre si añadir un estado es un cambio que rompe.

Cómo probar que no rompiste nada

La forma barata de tener red de seguridad es guardar respuestas reales y compararlas tras cada cambio.

# Guardar una respuesta de referencia, ordenando las claves para comparar estable
curl -s https://api.ejemplo.com/v1/pedidos/8841 | jq -S . > referencia.json

# Tras el cambio, comparar contra la referencia
curl -s http://localhost:8080/v1/pedidos/8841 | jq -S . > actual.json
diff referencia.json actual.json && echo "sin cambios en la respuesta"

# Solo interesa saber si DESAPARECIÓ alguna clave: lo añadido es compatible
diff <(jq -S 'paths(scalars) | join(".")' referencia.json) \
     <(jq -S 'paths(scalars) | join(".")' actual.json) | grep '^<'

Ese último comando es el que más rinde: lista solo los campos que existían y ya no están, que es la definición práctica de un cambio que rompe. Añadidos no aparecen, porque no rompen nada.

Con eso incorporado a las pruebas, la pregunta de si un cambio es compatible deja de responderse de memoria.

Cómo decidir sin quedarte atrapado

Para una API que solo consume tu propia aplicación, la recomendación es no versionar todavía y comprometerse a añadir sin quitar. Es el estado en el que están la mayoría de los proyectos pequeños y ponerle versiones desde el día uno añade trabajo sin resolver ningún problema existente.

En el momento en que un tercero se integra, conviene poner la versión en la ruta, aunque sea empezando por la uno, y fijar desde ese día la política: cuántas versiones vivas y cuánto dura el aviso de retirada.

La asimetría conviene tenerla presente: añadir una versión a una API sin versionar es un trabajo acotado, se monta la nueva ruta y se deja la antigua apuntando a la implementación actual. Quitar versiones que se prometieron sostener es mucho más caro, porque implica coordinar con gente que no controlas.

Qué se le escapa a una IA con esto

Pedir una API produce endpoints sin versión, sin formato de error consistente y sin paginación. Es la misma terna de siempre, y aparece porque nadie las mencionó y porque el ejemplo funciona sin ellas.

Cuando se pide versionado, lo habitual es que se añada la versión a la ruta y se duplique el controlador entero, incluso para los endpoints que no cambiaron. Eso funciona y multiplica el mantenimiento desde el primer día.

Las frases que cambian la respuesta son pedir que solo se duplique lo que de verdad cambia, que la versión antigua reutilice la implementación nueva donde coincidan, y que se devuelva una cabecera de obsolescencia en la versión que se va a retirar.

Y conviene preguntar explícitamente, antes de aceptar un cambio, si rompe a los clientes actuales y por qué. Si la respuesta no distingue entre añadir y quitar, es señal de que el cambio no se ha evaluado con ese criterio.


Comparativa intermedio