Versionado de API para apps móviles: evoluciona endpoints con seguridad
Versionado de API para apps móviles explicado con un plan de despliegue simple, cambios compatibles hacia atrás y pasos de deprecación para que versiones antiguas sigan funcionando.

Por qué los cambios en la API rompen a los usuarios móviles
Las apps móviles no se actualizan todas a la vez. Aunque publiques una corrección hoy, mucha gente seguirá usando una versión antigua durante días o semanas. Algunos apagan las actualizaciones automáticas. Otros tienen poco espacio. Algunos simplemente no abren la app con frecuencia. El tiempo de revisión de las tiendas y los despliegues por etapas añaden más retraso.
Esa brecha importa porque tu backend suele cambiar más rápido que los clientes móviles. Si el servidor cambia un endpoint y la app antigua sigue llamándolo, la app puede romperse aunque no haya cambiado nada en el teléfono del usuario.
La rotura rara vez aparece como un mensaje de error claro. Suele parecer dolor de producto cotidiano:
- El login o el registro falla tras un despliegue del backend
- Listas aparecen vacías porque un campo fue renombrado o movido
- La app se bloquea al leer un valor que falta
- Los pagos fallan porque la validación se endureció
- Funcionalidades desaparecen silenciosamente porque cambió la forma de la respuesta
El objetivo del versionado es sencillo: seguir mejorando el servidor sin obligar a todo el mundo a actualizar de inmediato. Trata tu API como un contrato de larga duración. Las nuevas versiones de la app deben funcionar con el comportamiento nuevo del servidor, y las versiones antiguas deben seguir funcionando el tiempo suficiente para el ciclo real de actualizaciones.
Para la mayoría de apps de consumo, espera soportar varias versiones de la app al mismo tiempo. Las apps internas pueden avanzar más rápido, pero raramente es instantáneo. Planear la superposición mantiene los despliegues por fases tranquilos en lugar de convertir cada release en una avalancha de soporte.
Qué significa “compatible” en un contrato de API
Un contrato de API es la promesa entre tu app móvil y tu servidor: qué URL llamar, qué entradas se aceptan, cómo es la respuesta y qué significa cada campo. Cuando la app depende de esa promesa y el servidor la cambia, los usuarios lo perciben como bloqueos, datos faltantes o funcionalidades que dejan de funcionar.
Un cambio es compatible cuando las versiones antiguas de la app pueden seguir usando la API sin tocar el código. En la práctica, eso significa que el servidor sigue entendiendo lo que las apps antiguas envían y sigue devolviendo respuestas que las apps antiguas pueden parsear.
Una manera rápida de separar cambios seguros de riesgosos:
- Cambios rompientes: eliminar o renombrar un campo, cambiar un tipo (número a cadena), convertir un campo opcional en obligatorio, cambiar el formato de errores, endurecer la validación de forma que las apps antiguas no cumplan.
- Cambios normalmente seguros: añadir un campo opcional nuevo, agregar un endpoint nuevo, aceptar formatos de petición antiguos y nuevos, añadir valores a un enum (solo si la app trata valores desconocidos como “otro”).
La compatibilidad también necesita un plan de fin de vida. Retirar comportamiento antiguo está bien, pero debe programarse (por ejemplo, “mantener v1 durante 90 días después del lanzamiento de v2”) para que puedas evolucionar sin sorprender a los usuarios.
Enfoques comunes de versionado y sus compensaciones
El versionado busca dar a las builds antiguas un contrato estable mientras avanzas. Hay varios enfoques comunes y cada uno desplaza la complejidad a un lugar distinto.
Versionado en la URL
Poner la versión en la ruta (como /v1/ y /v2/) es lo más fácil de ver y depurar. También funciona bien con cachés, logs y ruteo porque la versión forma parte de la URL. La desventaja es que los equipos pueden acabar manteniendo handlers paralelos más tiempo del esperado, incluso cuando la diferencia es pequeña.
Versionado por cabecera
Con el versionado por cabecera, el cliente envía la versión en una cabecera (por ejemplo, una cabecera Accept o una cabecera personalizada). Las URLs quedan limpias y puedes evolucionar sin cambiar cada ruta. La desventaja es la visibilidad: proxies, logs y humanos a menudo no ven la versión a menos que seas cuidadoso, y los clientes móviles deben establecer la cabecera en cada petición.
Versionado por parámetro de consulta
El versionado por query (como ?v=2) parece simple, pero se complica. Los parámetros se copian en marcadores, herramientas de analítica y scripts, y puedes acabar con múltiples “versiones” flotando sin una propiedad clara.
Si quieres una comparación rápida:
- Versionado por URL: lo más fácil de inspeccionar, pero puede crear APIs paralelas de larga vida
- Versionado por cabecera: URLs limpias, pero más difícil de depurar
- Versionado por query: rápido para empezar, fácil de usar mal
Los feature flags son otra herramienta. Permiten cambiar comportamiento detrás del mismo contrato (por ejemplo, un nuevo algoritmo de ranking) sin crear una nueva versión de la API. No reemplazan el versionado cuando cambia la forma de peticiones o respuestas.
Elige un enfoque y mantenlo. La consistencia importa más que la “mejor” elección.
Reglas prácticas para cambios compatibles hacia atrás
La mentalidad más segura es: los clientes antiguos deben seguir funcionando aunque nunca conozcan tu nueva característica. Eso normalmente significa añadir cosas, no cambiar lo que ya existe.
Prefiere cambios aditivos: campos nuevos, endpoints nuevos, parámetros opcionales nuevos. Cuando añades algo, que sea realmente opcional desde el servidor. Si una app antigua no lo envía, el servidor debería comportarse exactamente como antes.
Algunos hábitos que evitan la mayoría de las roturas:
- Añade campos, pero no cambies el tipo o el significado de campos existentes.
- Trata la ausencia de una entrada como normal y usa valores por defecto sensatos.
- Ignora campos desconocidos en las peticiones para que clientes viejos y nuevos coexistan.
- Mantén estable el formato de errores. Si debes cambiarlo, versiona el payload de error.
- Si el comportamiento debe cambiar, introduce un endpoint nuevo o una versión en lugar de un ajuste “silencioso”.
Evita cambiar el significado de un campo existente sin un bump de versión. Por ejemplo, si status=1 solía significar “pagado” y lo rehuyes a “autorizado”, las apps antiguas tomarán decisiones incorrectas y puede que no lo notes hasta que los usuarios se quejen.
Renombrar y eliminar requiere un plan. El patrón más seguro es mantener el campo antiguo y añadir el nuevo lado a lado durante un tiempo. Rellena ambos en las respuestas, acepta ambos en las peticiones y registra quién sigue usando el campo antiguo. Elimina el campo antiguo solo después de que termine la ventana de deprecación.
Un hábito pequeño pero poderoso: cuando introduces una nueva regla de negocio obligatoria, no hagas que el cliente sea responsable el primer día. Implanta la regla en el servidor con un valor por defecto primero, y más tarde exige que el cliente envíe el nuevo valor cuando la mayoría de usuarios ya se haya actualizado.
Establece una política simple de versionado y deprecación
El versionado funciona mejor cuando las reglas son aburridas y están por escrito. Mantén la política lo bastante corta para que producto, móvil y backend la sigan.
Empieza con ventanas de soporte. Decide cuánto tiempo mantendrás versiones antiguas de la API después de que salga una nueva (por ejemplo, 6-12 meses), además de excepciones (problemas de seguridad, cambios legales).
Luego define cómo avisas a los clientes antes de romperles. Elige una señal de deprecación y úsala siempre. Opciones comunes incluyen una cabecera de respuesta como Deprecation: true con una fecha de retiro, o un campo JSON como "deprecation": {"will_stop_working_on": "2026-04-01"} en respuestas seleccionadas. Lo que importa es la consistencia: los clientes pueden detectarlo, los dashboards pueden reportarlo y los equipos de soporte pueden explicarlo.
Define una versión mínima de app soportada y sé explícito sobre la aplicación de esa regla. Evita bloqueos sorpresa. Un enfoque práctico es:
- Devolver una advertencia blanda (por ejemplo, un campo que desencadene un aviso de actualización en la app).
- Hacer cumplimiento solo después de una fecha límite comunicada.
Si bloqueas peticiones, devuelve un payload de error claro con un mensaje para humanos y un código legible por máquina.
Finalmente, decide quién puede aprobar cambios rompientes y qué documentación se requiere. Mantenlo simple:
- Una sola persona aprueba cambios rompientes.
- Una nota corta de cambio explica qué cambió, quién se ve afectado y la ruta de migración.
- Un plan de pruebas incluye al menos una versión antigua de la app.
- Se fija una fecha de retiro cuando empieza la deprecación.
Plan de despliegue paso a paso que mantiene las apps antiguas funcionando
Los usuarios móviles no se actualizan el primer día, así que la forma más segura es publicar una nueva API manteniendo la antigua intacta y luego mover el tráfico gradualmente.
Primero, define qué cambia en v2 y bloquea el comportamiento de v1. Trata v1 como una promesa: mismos campos, mismos significados, mismos códigos de error. Si v2 necesita una forma de respuesta distinta, no modifiques v1 para que coincida.
Después, ejecuta v2 en paralelo. Eso puede significar rutas separadas (como /v1/... y /v2/...) o handlers separados detrás del mismo gateway. Mantén la lógica compartida en un lugar, pero conserva el contrato separado para que un refactor de v2 no cambie v1 accidentalmente.
Luego actualiza la app móvil para preferir v2. Incluye un fallback sencillo: si v2 responde “not supported” (u otro error conocido), intenta v1. Esto ayuda durante lanzamientos por etapas y cuando las redes reales se comportan de forma impredecible.
Tras publicar la app, monitoriza adopción y errores. Comprobaciones útiles incluyen:
- Volumen de peticiones v1 vs v2 por versión de app
- Tasa de errores y latencia en v2
- Fallos de parseo de respuestas
- Bloqueos ligados a pantallas que dependen de la red
Cuando v2 esté estable, añade advertencias de deprecación para v1 y comunica un cronograma. Retira v1 solo cuando el uso caiga por debajo de un umbral aceptable (por ejemplo, menos del 1-2% durante varias semanas).
Ejemplo: cambias GET /orders para soportar filtros y nuevos estados. v2 añade status_details mientras v1 se mantiene igual. La app nueva llama a v2, pero si ocurre un caso límite puede volver a v1 y aún así mostrar la lista de pedidos.
Consejos de implementación en el servidor
La mayoría de las roturas durante rollout ocurren porque el manejo de versiones está disperso entre controladores, helpers y código de base de datos. Mantén la decisión “¿qué versión es esta petición?” en un solo sitio y hace que el resto de la lógica sea predecible.
Coloca el ruteo por versión detrás de una única puerta
Elige una señal (segmento de URL, cabecera o número de build de la app) y normalízala al principio. Rutea al handler correcto en un único módulo o middleware para que cada petición siga el mismo camino.
Un patrón práctico:
- Parsear la versión una vez (y registrarla).
- Mapear la versión a un handler (
v1,v2, ...) en un registro único. - Mantener utilidades compartidas agnósticas a la versión (parseo de fechas, comprobaciones de auth), no la lógica de forma de respuesta.
Ten cuidado al compartir código entre versiones. Arreglar un bug de v2 en código “compartido” puede cambiar accidentalmente el comportamiento de v1. Si la lógica afecta campos de salida o reglas de validación, mantenla versionada o cúbrela con tests específicos por versión.
Mantén los cambios de datos compatibles durante el despliegue
Las migraciones de base de datos deben funcionar para ambas versiones al mismo tiempo. Añade columnas primero, backfillea si hace falta y solo más tarde elimina o endurece constraints. Evita renombrar o cambiar significados a mitad del rollout. Si necesitas cambiar formatos, considera escribir ambos formatos por un período corto hasta que la mayoría de clientes migre.
Haz que los errores sean predecibles. Las apps antiguas suelen tratar errores desconocidos como “algo salió mal”. Usa códigos de estado consistentes, identificadores de error estables y mensajes cortos que ayuden al cliente a decidir qué hacer (reintentar, re-autenticar, mostrar aviso de actualización).
Finalmente, protégete contra campos que las apps antiguas no envían. Usa valores por defecto seguros y valida con detalles de error claros y estables.
Consideraciones en la app móvil que afectan al versionado
Como los usuarios pueden quedarse en una build antigua durante semanas, el versionado debe asumir que múltiples versiones de cliente consultarán tus servidores a la vez.
Un gran acierto es la tolerancia en el cliente. Si la app se bloquea o falla el parseo cuando el servidor añade un campo, lo sentirás como bugs “aleatorios” durante el rollout.
- Ignora campos JSON desconocidos.
- Trata los campos faltantes como normales y usa valores por defecto.
- Maneja nulls de forma segura (los campos pueden volverse nulos durante migraciones).
- No dependas del orden de arrays a menos que el contrato lo garantice.
- Mantén el manejo de errores amigable para el usuario (un estado de reintento es mejor que una pantalla en blanco).
El comportamiento de red también importa. Durante el despliegue puedes tener versiones de servidor mezcladas detrás de load balancers o caches, y las redes móviles amplifican pequeños problemas.
Elige reglas claras de timeout y reintentos: timeouts cortos para llamadas de lectura, algo más largos para uploads, y reintentos limitados con backoff. Haz la idempotencia estándar para llamadas de creación o similares a pagos para que un reintento no duplique una operación.
Los cambios de autenticación son la forma más rápida de dejar fuera a apps antiguas. Si cambias el formato del token, scopes requeridos o reglas de sesión, mantén una ventana de solapamiento donde ambos tokens funcionan. Si debes rotar claves o claims, planifica una migración por fases, no un corte el mismo día.
Envía metadata de la app con cada petición (por ejemplo, versión de app y plataforma). Así es más fácil devolver advertencias dirigidas sin bifurcar toda la API.
Monitorización y despliegues por fases sin sorpresas
Un despliegue por fases solo funciona si puedes ver lo que hacen las distintas versiones de app. El objetivo es sencillo: saber quién sigue en endpoints antiguos y detectar problemas antes de que afecten a todos.
Empieza por monitorizar uso por versión de API cada día. No te quedes en contar peticiones totales. Monitoriza dispositivos activos y desglosa endpoints clave como login, perfil y pagos. Esto te indica si una versión vieja sigue “viva” aunque el tráfico total parezca pequeño.
Después vigila errores por versión y tipo. Un aumento en 4xx suele significar una desalineación de contrato (cambio de campo obligatorio, valores de enum desplazados, reglas de auth más estrictas). Un aumento en 5xx suele señalar regresiones del servidor (deploy malo, consultas lentas, fallos en dependencias). Ver ambos por versión te ayuda a elegir la corrección adecuada rápidamente.
Usa despliegues por etapas en las tiendas para limitar el blast radius. Incrementa la exposición en pasos y observa los mismos dashboards tras cada paso (por ejemplo, 5%, 25%, 50%). Si la versión nueva muestra problemas, detén el rollout antes de que se convierta en una caída total.
Ten disparadores de rollback escritos de antemano, no decididos durante un incidente. Disparadores comunes incluyen:
- tasa de error por encima de un umbral fijo durante 15-30 minutos
- caída en la tasa de éxito de login (o aumento en refresco de tokens fallidos)
- aumento de fallos en pagos (o incremento de timeouts en checkout)
- pico en tickets de soporte vinculados a una versión específica
- aumentos de latencia en un endpoint crítico
Mantén un playbook corto para incidentes relacionados con versiones: a quién avisar, cómo desactivar una flag riesgosa, qué release del servidor restaurar y cómo extender la ventana de deprecación si todavía hay clientes activos.
Ejemplo: evolucionando un endpoint en un release real
El checkout es un cambio clásico. Empiezas con un flujo simple, luego añades un nuevo paso de pago (por ejemplo, autentificación más fuerte) y renombras campos para alinear el lenguaje con el negocio.
Imagina que la app móvil llama a POST /checkout.
Qué se mantiene en v1 y qué cambia en v2
En v1, conserva la petición y el comportamiento existente para que versiones antiguas puedan completar pagos sin sorpresas. En v2, introduce el nuevo flujo y nombres más claros.
- v1 mantiene:
amount,currency,card_token, y una respuesta simple comostatus=paid|failed. - v2 añade:
payment_method_id(reemplazandocard_token) y un camponext_actionpara que la app pueda manejar un paso extra (verify, retry, redirect). - v2 renombra:
amountatotal_amountycurrencyabilling_currency.
Las apps antiguas siguen funcionando porque el servidor aplica valores por defecto seguros. Si una petición v1 no conoce next_action, el servidor completa el pago cuando es posible y devuelve el mismo resultado al estilo v1. Si el nuevo paso es obligatorio, v1 recibe un código de error claro y estable como requires_update en lugar de un fallo genérico confuso.
Adopción, retiro y rollback
Sigue la adopción por versión: qué porción de llamadas de checkout van a v2, tasas de error y cuántos usuarios aún usan builds que solo soportan v1. Cuando el uso de v2 sea consistentemente alto (por ejemplo, 95%+ varias semanas) y el uso de v1 sea bajo, fija una fecha de retiro para v1 y comunícala (notas de release, mensajes in-app).
Si algo sale mal tras el lanzamiento, el rollback debe ser rutinario:
- Rutar más tráfico al comportamiento de v1.
- Desactivar el nuevo paso de pago con una flag del lado servidor.
- Seguir aceptando ambos sets de campos y registrar lo que tuviste que convertir automáticamente.
Errores comunes que causan roturas silenciosas
La mayoría de fallos en APIs móviles no son ruidosos. La petición tiene éxito, la app sigue funcionando, pero los usuarios ven datos faltantes, totales erróneos o botones que no hacen nada. Estos problemas son difíciles de detectar porque suelen golpear versiones antiguas durante un despliegue por fases.
Causas comunes:
- Cambiar o eliminar campos (o su tipo) sin un plan de versión claro.
- Hacer obligatorio un campo nuevo de inmediato, de modo que las apps antiguas empiecen a ser rechazadas.
- Desplegar una migración de base de datos que asume que solo existe la app nueva.
- Retirar v1 basándose en instalaciones, no en uso activo.
- Olvidar jobs en background y webhooks que todavía envían payloads antiguos.
Un ejemplo concreto: tu campo de respuesta total era una cadena ("12.50") y lo cambias a número (12.5). Las apps nuevas van bien. Las antiguas pueden tratarlo como cero, ocultarlo o bloquearse solo en ciertas pantallas. Si no monitorizas errores por versión de app, puede pasar desapercibido.
Lista rápida y próximos pasos
El versionado trata menos de nombres ingeniosos de endpoints y más de repetir las mismas comprobaciones de seguridad en cada release.
Comprobaciones rápidas antes del release
- Mantén los cambios aditivos. No elimines ni renombres campos que las apps antiguas ya leen.
- Proporciona valores por defecto seguros para que la ausencia de campos nuevos se comporte como el flujo antiguo.
- Mantén estables las respuestas de error (estado + forma + significado).
- Trata los enums con cuidado y no cambies el significado de un valor existente.
- Reproduce unas pocas peticiones reales de versiones antiguas y confirma que las respuestas aún parsean.
Comprobaciones rápidas durante el rollout y antes del retiro
- Monitorea adopción por versión de app. Debe verse una curva clara de v1 a v2, no una línea plana.
- Vigila tasas de error por versión. Un pico suele indicar parseo o validación que rompió clientes antiguos.
- Arregla el endpoint con más fallos primero y luego amplia el despliegue.
- Retira solo cuando el uso activo sea realmente bajo y comunica la fecha.
- Elimina el código de fallback al final, después de la ventana de retiro.
Escribe tu política de versionado y deprecación en una sola página, luego convierte la checklist en una puerta de release que tu equipo siga en cada despliegue.
Si construyes herramientas internas o apps de cara al cliente con una plataforma no-code, sigue ayudando tratar la API como un contrato con una ventana de deprecación clara. Para equipos que usan AppMaster (appmaster.io), mantener v1 y v2 lado a lado suele ser más fácil porque puedes regenerar backend y clientes a medida que cambian los requisitos, manteniendo contratos antiguos activos durante el rollout.
FAQ
Los usuarios móviles no actualizan todos a la vez, así que versiones antiguas de la app siguen llamando a tu backend después de que despliegues cambios. Si modificas un endpoint, la validación o la forma de la respuesta, esas versiones antiguas no pueden adaptarse y fallan de formas que se ven como pantallas vacías, bloqueos o pagos que no se procesan.
“Compatible” significa que una app antigua puede seguir haciendo las mismas peticiones y recibir respuestas que pueda parsear y usar correctamente, sin cambios en su código. La forma más segura de pensarlo es que la API es un contrato: puedes añadir capacidades nuevas, pero no deberías cambiar el significado de campos o comportamientos que ya usan los clientes actuales.
Un cambio es rompedor cuando altera algo de lo que depende una app existente: quitar o renombrar campos, cambiar el tipo de un campo, endurecer la validación para que peticiones antiguas fallen, o cambiar el formato del payload de error. Si una app antigua no puede parsear la respuesta o no puede cumplir las reglas de la petición, el cambio es rompedor aunque el servidor esté “funcionando”.
El versionado por URL suele ser el valor por defecto más sencillo porque la versión es visible en logs, herramientas de depuración y en el ruteo; además es difícil olvidarse de enviarla. El versionado por cabecera funciona también, pero es más fácil pasarlo por alto durante el troubleshooting y obliga a que cada petición del cliente incluya la cabecera correcta.
Elige una ventana de soporte clara que refleje el comportamiento real de actualización móvil y cúmplela; muchos equipos optan por meses, no días. Lo importante es publicar una fecha de retiro y medir el uso activo para no apagar una versión antigua a ciegas.
Usa una señal de deprecación consistente para que clientes y dashboards la detecten con fiabilidad, por ejemplo, una cabecera de respuesta estable o un pequeño campo JSON que incluya la fecha de retiro. Manténlo simple y predecible para que soporte y producto lo puedan explicar sin indagar en la implementación.
Prefiere cambios aditivos: añade nuevos campos opcionales o nuevos endpoints y conserva el significado de los campos antiguos. Si necesitas renombrar, mantén ambos campos en paralelo durante un tiempo y rellénalos simultáneamente para que las apps antiguas no pierdan datos mientras las nuevas migran al nombre nuevo.
Diseña las migraciones de base de datos para que las dos versiones de la API puedan operar al mismo tiempo: añade columnas primero, rellena datos si hace falta y solo más tarde endurece restricciones o elimina campos antiguos. Evita renombrar o cambiar el significado en medio del despliegue, porque provocarás incompatibilidades entre versiones.
Haz la app tolerante: ignora campos JSON desconocidos, trata los campos faltantes como normales usando valores por defecto seguros y maneja nulls sin bloquear. Esto reduce errores “aleatorios” durante despliegues cuando el servidor añade campos o las respuestas cambian temporalmente.
Monitoriza uso y errores por versión de API y por versión de app, presta atención especial a login y pagos, y amplía el despliegue solo cuando los datos estén estables. Un plan seguro bloquea el comportamiento de v1, ejecuta v2 en paralelo y mueve clientes gradualmente con un fallback claro hasta que la adopción sea lo bastante alta como para retirar v1 con confianza.


