Saltearse al contenido

Versionado

Hay dos números y no miden lo mismo. La versión del producto es un semver como 0.88.0 (331); la versión de la API es la 1 que viaja en X-Api-Version. Una integración puede romperse por un cambio de producto antes de que la API cambie de número, y por eso conviene leer las dos.

El formato es X.Y.Z (N). N, entre paréntesis, es el número de compilación: los despliegues exitosos del backend. La versión dice qué compatibilidad tienes; el build, qué está vivo.

Merez son tres repos —backend, admin y web— con una sola versión y un solo tag, porque se despliegan juntos y comparten contrato. La fuente canónica es el tag de git; el CHANGELOG.md del backend es el registro. En este repositorio, la última etiqueta publicada es v0.88.0, es decir 0.88.0 (331).

Qué mueve cada dígito lo decide lo que recibe el usuario, no el tamaño del diff:

  • PATCH: correcciones, ajustes y trabajo interno —renombrados, deuda técnica, la migración al inglés—. Nada nuevo para quien consume.
  • MINOR: funcionalidad nueva.
  • MAJOR: incompatibilidades. Solo existe a partir de 1.0.0.

Semver, Regla 4: en 0.x todo puede cambiar en cualquier momento. No hay promesa de estabilidad. Una retirada —una clave, un valor de enum, una ruta— rompe igual a los sitios cliente y al panel, pero mientras Merez esté en 0.x cabe en un PATCH. Al publicar 1.0.0 eso deja de valer y una retirada pasa a exigir un MAJOR.

La práctica es aditiva: el CI no deja perder una clave sin anotarla.

/api/v1 es la ruta canónica; /api sigue viva y hoy devuelve exactamente lo mismo. Todas las respuestas llevan:

X-Api-Version: 1

Es un entero de config/api.php (hoy 1), no el semver. Es la versión del contrato HTTP.

La deprecación viene apagada (API_DEPRECATION_ENABLED=false). Si se enciende, solo las rutas que no son /api/v1 reciben las cabeceras:

Deprecation: true
Link: </api/v1/public/plans>; rel="successor-version"
Sunset: Sat, 31 Oct 2026 23:59:59 GMT

Sunset solo aparece si se configuró API_DEPRECATION_SUNSET. Las rutas de /api/v1 no se marcan nunca.

El contrato solo puede crecer: añadir una clave, una ruta o un parámetro opcional no rompe a nadie. Lo que rompe es quitar o mover lo que ya existía:

  • una clave de respuesta, o su tipo (incluido estrechar un campo que podía ser entero a solo null);
  • una ruta o un segmento literal de la URL;
  • una clave del cuerpo de un 422;
  • un valor de enum que viaja en la URL o como clave de un JSON;
  • el sobre del paginador (current_page, data, links, total…).

Los nombres de campo que la API acepta no están congelados como las respuestas, pero renombrarlos también rompe: el PATCH empieza a responder 200 sin guardar lo que ya no reconoce.

Ventana de terminal
curl -sD - -o /dev/null https://api.merez.co/api/v1/public/plans
# HTTP/1.1 200 OK
# X-Api-Version: 1

La versión del producto, en el tag y el changelog:

v0.88.0 → 0.88.0 (331)
  • Quitar una clave o cambiar su tipo. Es la ruptura clásica: el consumidor la lee y pasa a recibir undefined o un valor que no esperaba. En 0.x puede viajar en un PATCH; eso no la hace inocua.
  • Cambiar un valor de enum. Falla en silencio: ?status=borrador devuelve 200 con cero resultados, y una transición con el valor viejo en la URL da 404. Ningún error te avisa.
  • Renombrar un campo del 422. El formulario se queda mudo: el error llega, pero el frontend busca una clave que ya no existe.
  • Cambiar el sobre del paginador. Quien lee current_page o links en la raíz deja de encontrarlos.
  • Asumir que la API y el producto suben juntos. Un MINOR puede añadir una ruta sin tocar X-Api-Version; un PATCH puede retirar una clave. Mira el changelog, no solo la cabecera.
  • Clavar /api/ y no seguir el Link. Cuando la deprecación se encienda, el camino es la URL de rel="successor-version", no adivinar el prefijo.
  • Fallar por una clave desconocida. El contrato crece a propósito: ignora lo que no uses.
  • Contrato: cómo se congela la forma del JSON.
  • Errores: el código de estado que verás en cada caso.
  • Autenticación: qué credencial necesita cada versión.