Cómo Comparar Especificaciones OpenAPI y Bloquear Cambios Disruptivos en Integración Continua

Compara dos versiones de especificaciones OpenAPI para detectar cambios incompatibles en la API antes de la fusión. Usa oasdiff u openapi-diff en CI, luego cierra la brecha contractual con la CLI de Apidog.

Ashley Goolam

Ashley Goolam

16 June 2026

Cómo Comparar Especificaciones OpenAPI y Bloquear Cambios Disruptivos en Integración Continua

Apidog para empresas

Despliegue local

SSO & RBAC

Conforme con SOC 2

Explorar Apidog Enterprise

Una solicitud de extracción edita openapi.yaml. Las comprobaciones de CI se vuelven verdes. La especificación es válida, cumple con el linter y dos revisores la aprueban. Tres días después, un cliente móvil comienza a generar fallos por puntero nulo porque un campo de respuesta que solía estar ahí desapareció. Nadie lo eliminó a propósito. Alguien renombró una propiedad durante una refactorización, y nada en la revisión lo detectó.

Esta es la brecha que un validador simple nunca ve. Una especificación puede estar perfectamente bien formada y aun así romper a cada consumidor que dependa de ella. La única forma de saberlo es comparar la nueva especificación con la versión que reemplaza, cambio por cambio, y hacer una pregunta: ¿esto rompería a un cliente que funcionaba ayer? Esa comparación es un diff de OpenAPI, y ejecutarlo como una puerta de fusión es una de las comprobaciones de mayor retorno que puedes añadir a un repositorio de API.

button

Qué compara realmente un diff de OpenAPI

Un diff de OpenAPI toma dos especificaciones, una base y una cabecera, y reporta lo que cambió entre ellas. La base suele ser la especificación en tu rama objetivo (lo que está en vivo). La cabecera es la especificación que tu solicitud de extracción propone. Una buena herramienta de diff no solo muestra un delta textual como lo haría git diff. Comprende la estructura de OpenAPI, por lo que puede distinguir entre ediciones cosméticas y las que rompen el contrato.

Aquí está la distinción que importa. Algunos cambios son aditivos y seguros:

Los clientes existentes siguen funcionando a través de todos ellos. Envían lo que siempre enviaron y leen lo que siempre leyeron. Otros cambios son incompatibles con versiones anteriores, y estos son los que duelen:

El trabajo de una herramienta de diff de OpenAPI es escanear cada ruta, parámetro, esquema y respuesta en ambos documentos y clasificar cada cambio en una de esas categorías. Esa clasificación es el objetivo principal. Un diff de línea sin procesar entierra un campo required eliminado bajo cincuenta líneas de reformateo. Un diff estructural lo saca a la superficie como un cambio importante y te dice en qué ruta reside.

Si quieres el modelo mental subyacente de por qué algunos cambios rompen y otros no, la guía sobre cómo versionar y deprecación de APIs a escala cubre las reglas de compatibilidad en profundidad. La herramienta de diff es cómo aplicas esas reglas mecánicamente en lugar de esperar que un revisor las recuerde.

oasdiff: el caballo de batalla de código abierto

oasdiff es la herramienta de código abierto a la que la mayoría de los equipos recurren. Es un único binario de Go, es rápido y está construido específicamente en torno a la pregunta del cambio disruptivo. Lee documentos OpenAPI 3.0 y 3.1 y te ofrece algunos subcomandos dependiendo de lo que quieras obtener de la comparación.

Los tres que más usarás:

Para una puerta de fusión, breaking es lo que importa. Apúntalo a tu especificación base y a tu especificación de cabecera:

oasdiff breaking base-openapi.yaml head-openapi.yaml --fail-on ERR

base-openapi.yaml es la especificación de la rama objetivo y head-openapi.yaml es la que está en la solicitud de extracción. El subcomando breaking imprime solo los cambios incompatibles. El flag --fail-on ERR es lo que convierte esto en una puerta: hace que el comando salga con un estado distinto de cero cuando encuentra un cambio clasificado en el nivel ERR. La salida distinta de cero es la señal universal que CI lee como fallo.

Vale la pena entender ese modelo de severidad. oasdiff clasifica los cambios disruptivos en niveles, y ERR es el grave, un cambio que romperá clientes. WARN cubre cambios que podrían romper algunos clientes dependiendo de cómo estén escritos, e INFO es informativo. Tú decides dónde trazar la línea. --fail-on ERR bloquea solo las rupturas definitivas. --fail-on WARN es más estricto y también detecta los posibles.

Cuando quieres un resumen legible para un registro de cambios o un comentario de PR en lugar de un aprobado/fallido, el subcomando changelog ofrece una salida más amigable:

oasdiff changelog base-openapi.yaml head-openapi.yaml

oasdiff tiene algunos detalles realmente útiles. Realiza la coincidencia de endpoints que sobrevive a los parámetros de ruta renombrados, por lo que no marca {userId} convirtiéndose en {id} como una eliminación-más-adición cuando la ruta es idéntica por lo demás. Puede fusionar esquemas allOf antes de comparar para que la herencia no produzca ruido. Y emite más que texto plano: HTML, JSON, YAML y Markdown están disponibles a través de los flags de salida, lo que facilita alimentar el resultado a una anotación de CI o a un registro de cambios generado. Para una herramienta que puedes incorporar a una tubería en cinco minutos y confiar en que sea conservadora sobre lo que llama disruptivo, es difícil de superar.

openapi-diff: la alternativa JVM

Si tu pila ya funciona en la JVM, OpenAPITools/openapi-diff es una segunda opción sólida y digna de conocer. Es una herramienta basada en Java (Java 8 y superior) que compara dos especificaciones OpenAPI 3.x y renderiza la diferencia como HTML, Markdown, AsciiDoc, JSON o texto de consola. Puedes ejecutarla desde un jar compilado, a través de Maven, con Homebrew o como imagen de Docker, por lo que se adapta a una variedad de configuraciones de compilación sin mucho problema.

Su comparación profundiza en parámetros, respuestas, endpoints y métodos HTTP, y traza la misma línea que a todos les importa: cambios que mantuvieron la compatibilidad con versiones anteriores versus cambios que la rompieron. La CLI es sencilla:

openapi-diff old-openapi.yaml new-openapi.yaml --fail-on-incompatible

El flag --fail-on-incompatible sale con un código distinto de cero solo cuando un cambio rompe la compatibilidad con versiones anteriores, que es exactamente el comportamiento de puerta que deseas. Hay un --fail-on-changed más estricto si prefieres fallar ante cualquier cambio, y un modo --state que imprime solo no_changes, compatible o incompatible cuando quieres una respuesta de una sola palabra para scriptar.

Donde brilla es en la salida renderizada. Los informes HTML y Markdown son claros y detallados, lo que convierte a openapi-diff en una opción sólida cuando quieres un artefacto de diff que un humano realmente lea, no solo un código de salida de CI. La desventaja es la dependencia de la JVM y un inicio más pesado que un binario de Go. Si tu equipo ya usa Java, ese costo es cero y la herramienta se integra perfectamente. Si no, oasdiff es una opción más ligera. Ambas responden bien a la pregunta del cambio disruptivo; elige la que coincida con el tiempo de ejecución que ya mantienes.

Conectando el diff a CI como una puerta de fusión

Un diff que ejecutas manualmente no detecta nada, porque el momento en que olvidas ejecutarlo es el momento en que se envía la ruptura. La puerta debe residir en la tubería y activarse en cada solicitud de extracción que toque la especificación.

La única complicación en CI es que necesitas ambas versiones de la especificación presentes a la vez: la base de la rama objetivo y la cabecera de la PR. La extracción de la PR te da la cabecera. La base la sacas directamente del historial de git sin una segunda extracción:

name: openapi-diff
on:
  pull_request:
    paths:
      - "openapi.yaml"

jobs:
  breaking-changes:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Get base spec
        run: git show origin/${{ github.base_ref }}:openapi.yaml > base-openapi.yaml
      - name: Install oasdiff
        run: |
          curl -fsSL https://raw.githubusercontent.com/oasdiff/oasdiff/main/install.sh | sh
      - name: Diff for breaking changes
        run: oasdiff breaking base-openapi.yaml openapi.yaml --fail-on ERR

Algunos detalles soportan el peso aquí. fetch-depth: 0 extrae el historial completo para que git show pueda alcanzar la rama base. La línea git show origin/<base>:openapi.yaml lee la especificación tal como existe en la rama objetivo y la escribe en un archivo, sin necesidad de un clon adicional. El filtro paths significa que el trabajo solo se ejecuta cuando la especificación cambia realmente, por lo que las PR no relacionadas no lo pagan. Y el paso final es la puerta: si oasdiff breaking encuentra un cambio de nivel ERR, sale con un código distinto de cero, el trabajo se pone en rojo y la PR muestra una verificación fallida antes de que alguien haga clic en fusionar.

El autor ve precisamente qué cambio rompió la compatibilidad, en qué ruta, mientras el código aún está en revisión. Ese es todo el valor. La ruptura se detecta en el momento más barato posible en lugar de en el informe de fallos de un cliente.

No todos los cambios disruptivos son un error, por supuesto. A veces se está enviando una versión principal deliberada y la ruptura es intencional. El patrón limpio es proteger por defecto y requerir una anulación explícita para las excepciones: una etiqueta en la PR, un incremento de versión en info.version, o un flujo de trabajo aprobado separado. De esa manera, una ruptura es siempre una decisión que alguien tomó a propósito, nunca un accidente que se deslizó. La guía de estrategia de versionado de API explica cuándo una ruptura justifica una nueva versión principal y cuándo simplemente debe evitarse.

La brecha que un diff no puede cerrar

Aquí está el límite de cada herramienta mencionada, y es importante. Un diff compara dos archivos. Te dice que el nuevo documento es compatible con el anterior. No dice nada sobre si tu servicio en ejecución realmente coincide con alguno de ellos.

Esa es una falla diferente, y es la que más duele en producción. La especificación promete un campo created_at; la implementación dejó de devolverlo silenciosamente hace tres sprints. La especificación dice que un endpoint devuelve 200; el servicio en vivo devuelve 500 bajo una condición que nadie probó. El diff está limpio porque ambas versiones de la especificación están de acuerdo. El contrato y el código no. Un diff estático no tiene forma de saberlo, porque nunca se comunica con la API.

Cerrar esa brecha significa probar la API en vivo contra el contrato, no solo comparar el contrato consigo mismo. Generas pruebas a partir de la especificación, las ejecutas contra el servicio en ejecución y afirmas que las respuestas reales coinciden con las formas documentadas. Eso es la prueba de contrato, y es la capa que detecta la desviación entre lo que escribiste y lo que realmente enviaste.

Cerrándolo con Apidog y la CLI de Apidog

Apidog está construido para este ciclo, lo que lo convierte en un compañero natural para el paso de diff en lugar de un reemplazo. Importas o sincronizas tu especificación OpenAPI en un proyecto de Apidog, y Apidog puede generar escenarios de prueba directamente desde la especificación, con aserciones derivadas del esquema. Las pruebas comprueban que las respuestas reales coinciden con los tipos documentados, los campos requeridos y los códigos de estado. Construyes y mantienes esos escenarios visualmente en lugar de escribir a mano un conjunto paralelo de scripts de prueba que se desincronizan cada vez que el contrato cambia.

Debido a que Apidog mantiene el diseño, la simulación y las pruebas en un solo espacio de trabajo, la especificación sigue siendo la fuente de verdad en todos ellos. Puedes descargar Apidog e importar una especificación existente para probar el ciclo en tu propia API. Si aún estás decidiendo cómo mantener esa especificación bajo control entre versiones en primer lugar, el tutorial sobre control de versiones de una especificación OpenAPI con Git combina bien con este flujo de trabajo.

La CLI de Apidog es lo que ejecuta esos escenarios de forma autónoma en tu pipeline. Es un paquete de npm:

npm install -g apidog-cli

Ejecutas un escenario por ID, lo apuntas al entorno que quieres validar y pides un informe compatible con CI:

apidog run \
  --access-token $APIDOG_ACCESS_TOKEN \
  -t <scenarioId> \
  -e <environmentId> \
  -r junit,cli \
  --out-dir ./apidog-reports

El token de acceso autentica la ejecución y reside en un secreto de CI, nunca en un archivo comprometido. El flag -t selecciona el escenario, -e selecciona el entorno, y -r junit,cli emite XML de JUnit legible por máquina para tu panel de CI junto con salida de terminal legible para el registro de compilación. No adivines los IDs: copias el comando exacto, con los IDs reales del escenario y del entorno ya rellenados, desde la pestaña de CI/CD del escenario en Apidog. Si quieres la superficie de opciones completa, la guía completa de la CLI documenta cada flag, y apidog run --help los imprime bajo demanda.

El comportamiento de la puerta es el mismo principio que el diff. Cuando una aserción falla, porque una respuesta en vivo ya no coincide con el contrato, apidog run sale con un código distinto de cero. CI lee el código de salida, marca el paso como fallido y bloquea la fusión. Sin configuración adicional. Mientras el paso de ejecución esté en el pipeline, una regresión de contrato detiene la línea de la misma manera que lo hace un diff de cambio importante.

La secuencia completa previa a la fusión

Une las dos mitades y obtendrás un pipeline que detecta ambos tipos de fallos. El diff detecta los cambios que romperían un cliente al leer la especificación. La prueba de contrato detecta un servicio que ya no cumple la especificación al ejercitar la API en ejecución. Ejecútalos como trabajos separados:

jobs:
  breaking-changes:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - run: git show origin/${{ github.base_ref }}:openapi.yaml > base-openapi.yaml
      - run: curl -fsSL https://raw.githubusercontent.com/oasdiff/oasdiff/main/install.sh | sh
      - run: oasdiff breaking base-openapi.yaml openapi.yaml --fail-on ERR

  contract-conformance:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npm install -g apidog-cli
      - name: Run contract tests
        run: |
          apidog run \
            --access-token "$APIDOG_ACCESS_TOKEN" \
            -t 605067 \
            -e 1629989 \
            -r junit,cli \
            --out-dir ./apidog-reports
        env:
          APIDOG_ACCESS_TOKEN: ${{ secrets.APIDOG_ACCESS_TOKEN }}
      - name: Upload report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: apidog-report
          path: ./apidog-reports

Los dos trabajos se ejecutan en paralelo. El trabajo de diff lee archivos y no necesita nada más que git, por lo que finaliza en segundos. El trabajo de conformidad necesita un entorno accesible, por lo que generalmente se ejecuta contra una compilación de staging desplegada. El if: always() en la carga mantiene el informe fluyendo incluso cuando las pruebas fallan, que es exactamente cuando quieres leerlo. Si cualquiera de los trabajos se pone en rojo, la PR se bloquea. Para más información sobre cómo ejecutar la CLI en pipelines reales, la guía de Acciones de GitHub de la CLI de Apidog y el tutorial más amplio de pipelines CI/CD profundizan en la configuración.

button

Practica el diseño de API en Apidog

Descubre una forma más fácil de construir y usar APIs