Arazzo 1.1: qué hay de nuevo y qué funciona hoy
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:
| Cambio | Qué hace |
|---|---|
type: asyncapi como fuente | Una descripción de origen ahora puede ser un documento AsyncAPI, además de openapi y arazzo. |
channelPath, action, correlationId, timeout | Pasos 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. |
dependsOn | Un paso puede listar otros pasos que deben terminar antes de que empiece. Listar un paso no lo ejecuta. |
parameters en acciones | Una acción de éxito o fallo que llama a otro workflow puede pasarle entradas, por ejemplo para renovar un token y luego reintentar. |
| Selector Object | Extrae 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 Object | Fija 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: querystring | Envía un parámetro como la query string entera, igual que la misma novedad de OpenAPI 3.2. |
$self | Una 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.
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.productIdDescá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:
- products.arazzo.yaml
- testingapis-products.openapi.json (nuestro documento OpenAPI en vivo, recortado a los endpoints de productos y echo, con una entrada
serverspara que el runner sepa adónde enviar las peticiones)
$ npx @redocly/cli@2.62.1 respect products.arazzo.yamlRunning 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: 922msAlgunas partes del archivo hacen más de lo que parece:
$response.body#/data/ides 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
readProductusa 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:
✗ 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 == 404El 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:
- 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.ratingredocly lint da este archivo por bueno. El endpoint echo debería recibir {"rating": 4.3}. Esto es lo que le llegó:
{
"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:
outputs:
rating: $response.body#/rating/rateLos 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.
- 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 foundEl 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:
"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.1 | Lint | Ejecución |
|---|---|---|
arazzo: 1.1.0 | Aceptado | Se ejecuta |
$self | Aceptado | Aceptado; no hay nada que observar al ejecutar |
| Expression Type Object | Aceptado | Se ejecuta, y falla cuando debe |
dependsOn | Aceptado | Aceptado; los pasos se ejecutan en orden de todos modos |
| Selector Object | Aceptado | No se aplica: el objeto se envía como datos |
parameters en acciones | Aceptado | El workflow se ejecuta, las entradas llegan vacías |
| Pasos AsyncAPI | Sin probar | Sin 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.0y las novedades que se ejecutan: Expression Type Objects,$selfydependsOn. - 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
parametersen 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
200detectaron 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.