Arazzo 1.1: qué hay de nuevo y qué funciona hoy

Compartir

El 19 de mayo de 2026 la OpenAPI Initiative publicó Arazzo 1.1.0. Arazzo es el formato de la OpenAPI Initiative para describir una secuencia de llamadas a una API: iniciar sesión, luego crear un pedido, luego pagarlo, con cada paso pasando valores al siguiente. OpenAPI dice qué acepta y devuelve un endpoint. Arazzo dice a qué endpoints llamar, en qué orden y cómo saber si cada llamada funcionó.

La versión 1.1 es la primera con novedades desde la 1.0, de 2024. Escribimos un workflow 1.1 contra nuestra propia API y lo ejecutamos con Redocly CLI 2.62.1, publicado el 9 de octubre. El runner acepta documentos 1.1, y un workflow sencillo de crear, leer, actualizar y borrar pasa. Dos de las novedades pasan el linter pero se ignoran cuando el workflow se ejecuta de verdad. Esta entrada cuenta qué ha cambiado, incluye un workflow que puedes descargar y ejecutar, y señala las dos novedades que conviene evitar por ahora.

Qué hace un archivo Arazzo

Un documento Arazzo apunta a una o varias descripciones de API, normalmente archivos OpenAPI, y lista workflows. Cada workflow es una lista de pasos. Un paso nombra una operación por su operationId, rellena parámetros y un cuerpo de petición, dice qué cuenta como éxito y guarda valores de la respuesta para pasos posteriores.

Ese único archivo resulta útil en tres sitios:

  • Tests. Un runner ejecuta los pasos contra un servidor real y falla si no se cumple un criterio de éxito. Así lo usamos más abajo.
  • Documentación. Quien lee ve el orden de llamadas que necesita una tarea, algo que un archivo OpenAPI no puede expresar.
  • Agentes de IA. Un agente que lee OpenAPI sabe qué hace cada endpoint, pero tiene que adivinar cómo combinarlos. Un workflow Arazzo se lo dice.

Qué ha cambiado en Arazzo 1.1

Lo más llamativo es el soporte de AsyncAPI, para que un workflow mezcle llamadas HTTP con canales de mensajes. Casi todo lo demás hace más fácil escribir workflows HTTP. Están todos en el anuncio de la versión:

CambioQué hace
type: asyncapi como fuenteUna descripción de origen ahora puede ser un documento AsyncAPI, además de openapi y arazzo.
channelPath, action, correlationId, timeoutPasos que envían o reciben mensajes. action es send o receive, correlationId empareja una respuesta con su petición y timeout es un límite en milisegundos.
dependsOnUn paso puede listar otros pasos que deben terminar antes de que empiece. Listar un paso no lo ejecuta.
parameters en accionesUna acción de éxito o fallo que llama a otro workflow puede pasarle entradas, por ejemplo para renovar un token y luego reintentar.
Selector ObjectExtrae un valor de una respuesta o de una entrada con JSONPath, XPath o JSON Pointer. Funciona en outputs, valores de parámetros y cuerpos de petición.
Expression Type ObjectFija la versión de un lenguaje de consulta, como JSONPath según la RFC 9535 o XPath 3.1. Sustituye al Criterion Expression Type Object de la 1.0.
in: querystringEnvía un parámetro como la query string entera, igual que la misma novedad de OpenAPI 3.2.
$selfUna URI que identifica el documento, para que las referencias a él no dependan de dónde esté guardado el archivo.

La especificación también ganó una gramática completa para las expresiones de runtime como $steps.login.outputs.token, un orden definido para resolver las descripciones de origen y reglas exactas sobre cuándo una condición cuenta como verdadera. No añaden campos, pero zanjan casos en los que dos runners podían no estar de acuerdo. El soporte de gRPC, GraphQL, MCP y bucles está en la hoja de ruta para versiones posteriores y no está en la 1.1.

Un workflow que puedes ejecutar contra nuestra API

Este workflow crea un producto en api.testingapis.com, lo vuelve a leer, cambia su precio, lo borra y comprueba que ha desaparecido. Solo usa endpoints gratuitos, así que se ejecuta sin cuenta ni clave de API.

products.arazzo.yaml
arazzo: 1.1.0
$self: https://testingapis.com/downloads/arazzo/products.arazzo.yaml
info:
  title: Product lifecycle on testingapis.com
  summary: Create a product, read it back, change its price, delete it and check that it's gone.
  version: 1.0.0
sourceDescriptions:
  - name: testingapis
    url: ./testingapis-products.openapi.json
    type: openapi
workflows:
  - workflowId: product-lifecycle
    inputs:
      type: object
      properties:
        title: { type: string, default: Arazzo test product }
        price: { type: number, default: 12.5 }
        newPrice: { type: number, default: 9.99 }
    steps:
      - stepId: createProduct
        operationId: Products_Create
        requestBody:
          contentType: application/json
          payload:
            title: $inputs.title
            price: $inputs.price
            category: electronics
        successCriteria:
          - condition: $statusCode == 201
        outputs:
          productId: $response.body#/data/id

      - stepId: readProduct
        operationId: Products_GetById
        parameters:
          - name: id
            in: path
            value: $steps.createProduct.outputs.productId
        successCriteria:
          - condition: $statusCode == 200
          - context: $response.body
            condition: $[?@.category == 'electronics' && @.inStock == false]
            type:
              type: jsonpath
              version: rfc9535
        onFailure:
          - name: cleanUp
            type: goto
            stepId: deleteProduct

      - stepId: changePrice
        operationId: Products_Update
        parameters:
          - name: id
            in: path
            value: $steps.createProduct.outputs.productId
        requestBody:
          contentType: application/json
          payload:
            title: $inputs.title
            price: $inputs.newPrice
            category: electronics
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/data/price == $inputs.newPrice
        onFailure:
          - name: cleanUp
            type: goto
            stepId: deleteProduct

      - stepId: deleteProduct
        operationId: Products_Delete
        parameters:
          - name: id
            in: path
            value: $steps.createProduct.outputs.productId
        successCriteria:
          - condition: $statusCode == 200

      - stepId: confirmGone
        operationId: Products_GetById
        parameters:
          - name: id
            in: path
            value: $steps.createProduct.outputs.productId
        successCriteria:
          - condition: $statusCode == 404
    outputs:
      productId: $steps.createProduct.outputs.productId

Descárgalo junto con el archivo OpenAPI al que apunta, pon los dos en una carpeta y ejecútalo. Basta con Node 22.12 o posterior, porque npx se descarga el runner:

Terminal
$ npx @redocly/cli@2.62.1 respect products.arazzo.yaml
Salida (13 de octubre de 2026)
Running workflow products.arazzo.yaml / product-lifecycle

✓ POST /api/Products - step createProduct
  ✓ success criteria check - $statusCode == 201
  ✓ status code check - $statusCode in [200, 201, 400, 500]

✓ GET /api/Products/{id} - step readProduct
  ✓ success criteria check - $statusCode == 200
  ✓ success criteria check - $[?@.category == 'electronics' && @.inStock == fal...
  ✓ status code check - $statusCode in [200, 404, 500]

✓ PUT /api/Products/{id} - step changePrice
  ✓ success criteria check - $statusCode == 200
  ✓ success criteria check - $response.body#/data/price == $inputs.newPrice
  ✓ status code check - $statusCode in [200, 404, 500]

✓ DELETE /api/Products/{id} - step deleteProduct
  ✓ success criteria check - $statusCode == 200
  ✓ status code check - $statusCode in [200, 404, 500]

✓ GET /api/Products/{id} - step confirmGone
  ✓ success criteria check - $statusCode == 404
  ✓ status code check - $statusCode in [200, 404, 500]

Workflows: 1 passed, 1 total
Steps: 5 passed, 5 total
Checks: 12 passed, 12 total
Time: 922ms

Algunas partes del archivo hacen más de lo que parece:

  • $response.body#/data/id es una expresión de runtime con un JSON Pointer después del #. Nuestro endpoint de creación envuelve el producto nuevo en { message, data }, así que el ID está en /data/id. Cada paso posterior lo lee como $steps.createProduct.outputs.productId.
  • El criterio de readProduct usa el nuevo Expression Type Object para decir que la consulta es JSONPath según la RFC 9535. Una condición JSONPath pasa cuando el filtro coincide con al menos un nodo, así que $[?@.category == 'electronics' && @.inStock == false] comprueba dos campos a la vez. También invertimos los valores esperados para asegurarnos de que la comprobación falla cuando debe, y falló.
  • La tercera comprobación de cada paso, status code check, no está en el archivo Arazzo. Redocly la añade a partir de las respuestas que lista el archivo OpenAPI, así que un código de estado que la API no documenta hace fallar el paso aunque pasen tus propios criterios.

El workflow limpia cuando falla un paso

Los productos creados en nuestra API son compartidos: cualquiera que liste productos ve el tuyo hasta que se borra. Un test que se para a medias dejaría basura, así que readProduct y changePrice tienen una acción onFailure que salta a deleteProduct. Para comprobarlo, cambiamos la comprobación del precio para que no pudiera pasar:

Salida con un paso que falla
✗ PUT /api/Products/{id} - step changePrice
  ✓ success criteria check - $statusCode == 200
  ✗ success criteria check - $response.body#/data/price == $inputs.price
  ✓ status code check - $statusCode in [200, 404, 500]

Running failure action cleanUp for the step changePrice

✓ DELETE /api/Products/{id} - step deleteProduct
  ✓ success criteria check - $statusCode == 200

✓ GET /api/Products/{id} - step confirmGone
  ✓ success criteria check - $statusCode == 404

El workflow sigue informando de un fallo, pero antes borra el producto. Haz lo mismo en cualquier workflow que cree datos en un entorno compartido.

Los Selector Objects pasan el linter pero no se ejecutan

Un Selector Object es la nueva forma de sacar un valor de una respuesta. Usamos uno para leer la valoración de un producto con un JSON Pointer y enviarla a nuestro endpoint echo, que devuelve todo lo que recibe:

selector.arazzo.yaml (pasos)
- stepId: getProduct
  operationId: Products_GetById
  parameters:
    - { name: id, in: path, value: 1 }
  outputs:
    rating:
      context: $response.body
      selector: /rating/rate
      type: jsonpointer
- stepId: echoIt
  operationId: Echo_Echo_POST
  requestBody:
    contentType: application/json
    payload:
      rating: $steps.getProduct.outputs.rating

redocly lint da este archivo por bueno. El endpoint echo debería recibir {"rating": 4.3}. Esto es lo que le llegó:

Cuerpo de la petición que envió el runner
{
  "rating": {
    "context": {
      "id": 1,
      "title": "Wireless Noise-Cancelling Headphones",
      "price": 249.99,
      ...
      "rating": { "rate": 4.3, "count": 127 },
      "inStock": true,
      "createdAt": "2025-06-12T10:30:00Z"
    },
    "selector": "/rating/rate",
    "type": "jsonpointer"
  }
}

El runner resolvió context como el cuerpo entero de la respuesta y luego envió el propio Selector Object en lugar de aplicar el selector. Nada en la salida avisa de ello. El paso falla solo porque comprobamos lo que recibió el servidor; un workflow que solo compruebe un 200 pasaría enviando datos equivocados.

Hasta que los runners se pongan al día, usa la forma de la 1.0. Para un JSON Pointer, además, es más corta:

Funciona en 1.0 y 1.1
outputs:
  rating: $response.body#/rating/rate

Los parámetros que se pasan a un workflow se pierden

El ejemplo que usa el anuncio para parameters es una acción de fallo que llama a un workflow de renovación del token y reintenta el paso. Nuestros endpoints gratuitos no emiten tokens, así que probamos el mismo mecanismo con un producto que no existe: la acción de fallo llama a un workflow report que publica su entrada reason en el endpoint echo.

params.arazzo.yaml (paso)
- stepId: getMissing
  operationId: Products_GetById
  parameters:
    - { name: id, in: path, value: 99999 }
  successCriteria:
    - condition: $statusCode == 200
  onFailure:
    - name: reportAndRetry
      type: retry
      retryLimit: 1
      workflowId: report
      parameters:
        - name: reason
          value: not found

El runner sí llama al workflow report y luego reintenta el paso, en el orden que describe la especificación. Pero el endpoint echo recibió un objeto vacío:

Lo que recibió el endpoint echo
"contentType": "application/json",
"contentLength": 2,
"body": "{}",
"json": {}

reason nunca llegó. Ejecutar report por sí solo con --input reason=... funciona, así que el problema está en pasar parameters desde la acción. En una renovación de token, esto significa que la llamada de renovación sale sin refresh token.

Qué se ejecutó en Redocly CLI 2.62.1

Novedad de Arazzo 1.1LintEjecución
arazzo: 1.1.0AceptadoSe ejecuta
$selfAceptadoAceptado; no hay nada que observar al ejecutar
Expression Type ObjectAceptadoSe ejecuta, y falla cuando debe
dependsOnAceptadoAceptado; los pasos se ejecutan en orden de todos modos
Selector ObjectAceptadoNo se aplica: el objeto se envía como datos
parameters en accionesAceptadoEl workflow se ejecuta, las entradas llegan vacías
Pasos AsyncAPISin probarSin probar

No encontramos ninguna entrada del changelog de Redocly que prometa soporte de Arazzo 1.1, así que puede que simplemente sean funciones que aún no se han construido. Las otras herramientas que aparecen en el repositorio de Arazzo pueden comportarse de otra forma, y solo probamos Redocly.

Escribir workflows 1.1 hoy

  • Usa arazzo: 1.1.0 y las novedades que se ejecutan: Expression Type Objects, $self y dependsOn.
  • Lee los valores de la respuesta con expresiones de runtime como $response.body#/data/id, no con Selector Objects.
  • Resuelve la renovación del token volviendo a iniciar sesión en un paso, no con parameters en una acción de fallo.
  • Comprueba lo que recibió el servidor, no solo el código de estado. Ni un lint que pasa ni un 200 detectaron los problemas de esta entrada. Nuestro endpoint echo sí.
  • Fija la versión del runner, como hicimos con @redocly/cli@2.62.1, para que una actualización no cambie tus resultados sin un commit.

Si tu workflow prueba un ciclo de crear, leer, actualizar y borrar, nuestra lección de testing CRUD cubre qué comprobaciones necesita cada paso. Leer una especificación OpenAPI (en inglés) explica los valores operationId a los que se refiere un workflow, y la documentación de la API de productos lista cada campo que toca el workflow de arriba.