Buenas prácticas de versionado de API, para los llamantes
8 min de lectura
El versionado de API es la práctica de mantener funcionando un contrato antiguo después de cambiarlo, para que los llamantes puedan avanzar según su propio calendario y no el tuyo. Esa frase contiene las dos decisiones que importan: qué cuenta como cambiar el contrato, y cuánto tiempo sigue funcionando el antiguo. Dónde vive el número de versión, sobre lo que trata la mayoría de los debates de versionado, es la menos importante de las tres y la más fácil de acertar.
¿Cuándo se debería versionar una API?
Versiona una API solo cuando un cambio rompería a un llamante correcto. Los cambios aditivos, campos nuevos, endpoints nuevos, parámetros opcionales nuevos, no necesitan versión; los llamantes escritos contra el contrato antiguo siguen funcionando y la capacidad nueva simplemente está ahí. Un cambio que rompe algo sí necesita una, porque la alternativa es que un llamante se entere por un error. Versionar cada lanzamiento, incluidos los aditivos, enseña a los llamantes que las versiones son ruido, y dejan de leer los avisos que importan.
La prueba práctica es la misma del artículo de cambios que rompen algo: si un llamante que dependía solo del comportamiento documentado tiene que cambiar algo para seguir funcionando, el cambio necesita una versión. Si no, lánzalo bajo la versión actual y escribe una entrada de changelog.
¿Qué esquema de versionado de API debería usarse?
Usa el esquema que tus llamantes puedan ver y fijar con más facilidad, que para la mayoría de las APIs públicas es una versión en la ruta de la URL o un header de versión datado. Los cuatro esquemas comunes se diferencian menos en capacidad que en lo que le piden al llamante, y esa es la base correcta para elegir.
| Esquema | Ejemplo | Qué debe hacer el llamante | Quién lo usa |
|---|---|---|---|
| Ruta de URL | /v2/invoices | Cambiar la URL al migrar | La mayoría de las APIs REST públicas |
| Header de versión | X-GitHub-Api-Version: 2022-11-28 | Enviar un header, o aceptar el por defecto | GitHub |
| Versión de cuenta datada | Stripe-Version: 2026-08-26 | Fijar una fecha por petición o por cuenta | Stripe |
| Parámetro de consulta | /invoices?version=2 | Añadir un parámetro | APIs antiguas; hoy raramente elegido |
| Media type | Accept: application/vnd.example.v2+json | Negociar tipos de contenido | Puristas; pocos llamantes lo dominan |
Ruta de URL es el más visible y el menos flexible. Cualquier llamante puede ver en qué versión está leyendo una línea de log, y un salto de versión es un buscar-y-reemplazar. El costo: toda la superficie se mueve a la vez, no puedes cambiar el contrato de un solo endpoint sin acuñar una versión nueva para todos, así que las versiones de ruta tienden a ser raras y grandes.
Header de versión mantiene las URLs estables y deja que el servidor elija un valor por defecto
para llamantes que no envían nada, tal como funciona el
versionado de la API REST de GitHub:
una versión nombrada por fecha en X-GitHub-Api-Version, con la versión soportada más antigua como
por defecto para que los llamantes sin versión no se rompan. El costo: la versión es invisible en
una URL y fácil de olvidar en un cliente nuevo.
Versión de cuenta datada es el esquema de header más una adición: la versión se guarda contra
la cuenta, así que cada petición la obtiene sin enviar nada. El
versionado de API de Stripe fija cada cuenta a la
versión con la que se creó y deja que una petición lo sobrescriba con Stripe-Version. Es el
esquema más amigable para el llamante y el que más trabajo da operar, porque el servidor tiene que
traducir entre cada versión soportada y la actual.
Parámetro de consulta y media type funcionan ambos y fallan ambos la prueba de visibilidad de forma distinta: un parámetro de consulta se pierde fácilmente al construir una URL, y una versión de media type es invisible para casi cualquier herramienta con la que un llamante depure. El esquema datado de Stripe es el ejemplo más conocido del enfoque por fechas, y cómo versiona Stripe su API lo recorre paso a paso.
¿Cómo se hace el versionado de API en la práctica?
En la práctica una versión es un conjunto nombrado de comportamientos, y el servidor mapea cada petición a uno de ellos. Los pasos son los mismos sea cual sea el esquema que lleve el nombre.
- Nombra las versiones por fecha o por entero, no por versión semántica. Una API web no es un
paquete. Los llamantes no pueden fijar una versión menor de una URL, así que
v2o2026-08-26dice todo lo que un llamante necesita, y el versionado semántico implica una promesa de compatibilidad que el esquema no puede cumplir. - Mantén la versión fuera del código que no la necesita. Una versión debería seleccionar una capa de traducción en el borde, no bifurcar la lógica de negocio. Dos copias completas de la base de código es cómo una versión termina sin mantenimiento.
- Dale a cada versión un valor por defecto y un documento. Los llamantes que no envían versión reciben la más antigua soportada, nunca la más nueva, para que un cliente sin fijar no se rompa el día del lanzamiento. Cada versión tiene una página que dice qué cambió respecto a la anterior.
- Fija una ventana de soporte y publícala. La guía de versionado de Google, AIP-185, pide un período de transición razonable y bien comunicado, y recomienda 180 días incluso para funcionalidad beta. Elige una ventana, escríbela, y aplícala sin renegociar por versión.
- Retira versiones como retiras endpoints. Una versión pasada su ventana recibe el mismo trato
que cualquier API deprecada: un anuncio, un header
Sunset(RFC 8594) en cada respuesta, un recordatorio a mitad de camino a los llamantes que quedan, y una fecha de eliminación que se cumple.
¿Qué son v1 y v2 en una API REST?
v1 y v2 son nombres para dos contratos que el mismo servidor soporta al mismo tiempo. Un v2
existe porque algo en v1 no se podía cambiar sin romper a sus llamantes, así que el cambio fue a
un contrato nuevo y el antiguo siguió funcionando. Los números no implican que v2 esté completo
o que v1 esté muerto; ambas cosas son ciertas solo si la documentación lo dice. Un v3 que
aparece cada trimestre es una señal de que se están versionando cambios aditivos, o de que el
contrato nunca se diseñó para absorber cambios. gRPC
resuelve el mismo problema de otra forma: cambios de API en gRPC y Protobuf
cubre el versionado a través del nombre del paquete en un archivo .proto en vez de una ruta de
URL, y un formato de wire donde renombrar un campo es gratis pero renumerarlo es un breaking
change que ningún llamante REST reconocería como riesgoso.
¿Qué debería anunciar un cambio de versión?
Un cambio de versión debería anunciar qué rompe, a quién afecta, cómo migrar, y cuánto tiempo sigue funcionando la versión anterior. La entrada tiene la misma forma que cualquier otra entrada de cambio que rompe algo, más una línea con la ventana de soporte. Aquí una para una API versionada por header:
La versión de API 2026-11-01 está disponible. La versión 2025-06-15 se soporta hasta el 1 de noviembre de 2027. Nuevo en 2026-11-01:
GET /invoicesdevuelveamounten unidades mínimas como entero en vez de string decimal, y el campo deprecadocustomer_namese elimina a favor del objetocustomer. Afecta a llamantes en 2025-06-15 que parseanamountcomo string, que es el por defecto para clientes sin fijar creados antes de junio de 2025. Migración: parseaamountcomo entero y lee el nombre decustomer.name. FijaX-Api-Version: 2026-11-01cuando estés listo. Nada cambia para llamantes que no fijan versión.
La última frase es la que deja que la mayoría de las lectoras dejen de leer, y pertenece a todo anuncio de versión. La página de ejemplos de changelog incluye entradas de APIs que versionan así, y la diferencia entre las buenas y el resto está mayormente en esa última frase.
¿A quién se le avisa cuando cambia una versión?
A todos en la versión antigua, individualmente, y al changelog para todos los demás. Un cambio de versión es el único caso en que “publicamos algo sobre eso” garantizadamente se pierde a los llamantes que importan: los que fijaron una versión hace dos años y no han leído una nota de versión desde entonces. Los datos de uso responden quiénes son; el aviso tiene que llegarles donde está su código, en los headers de respuesta y en un mensaje a la dueña de la cuenta.
En el ciclo que ejecutamos, la entrada que anuncia una versión se redacta a partir del pull request que la lanza, la revisa una persona, y se publica en feed y widget, donde un cliente versionado puede leerla como JSON. Cualquier persona cuyo feedback en el widget pidió el cambio, o reportó el error que resuelve, y se convirtió en un issue de GitHub que cierra el pull request, recibe aviso en ese issue en cuanto la entrada se publica. El mecanismo es el mismo que para cualquier entrada; un salto de versión es solo la entrada con la apuesta más alta.
FAQ
¿Debería cada cambio de API recibir una versión nueva? No. Solo los cambios que rompen algo. Los cambios aditivos se lanzan bajo la versión actual con una entrada de changelog. Versionar los cambios aditivos entrena a los llamantes a ignorar las versiones.
¿Es mejor el versionado por URL o por header? El versionado por URL es más fácil de ver para los llamantes y más difícil de evolucionar poco a poco para ti; el versionado por header es al revés. Para una API pública con muchos clientes pequeños, el versionado por URL falla menos. Para una API grande con capa de traducción, la versión datada por header escala mejor.
¿Cuántas versiones deberían soportarse a la vez? Las menos que permita tu ventana de soporte, y nunca un número ilimitado. Dos o tres versiones concurrentes es normal; más que eso suele significar que las versiones no se están retirando.
¿Qué deberían recibir las peticiones sin versión? La versión soportada más antigua, para que los clientes existentes sin fijar sigan funcionando, con un header de respuesta que les diga qué versión recibieron.
Las afirmaciones técnicas de este artículo no se han revisado de forma independiente. Si algo está mal, avísanos y lo corregiremos.