Arazzo 1.1 : les nouveautés, et ce qui tourne aujourd’hui
Le 19 mai 2026, l’OpenAPI Initiative a publié Arazzo 1.1.0. Arazzo est le format de l’OpenAPI Initiative pour décrire une suite d’appels d’API : se connecter, puis créer une commande, puis la payer, chaque étape passant des valeurs à la suivante. OpenAPI dit ce qu’un point d’accès accepte et renvoie. Arazzo dit quels points d’accès appeler, dans quel ordre, et comment savoir si chaque appel a fonctionné.
La version 1.1 est la première à apporter des nouveautés depuis la 1.0 de 2024. Nous avons écrit un workflow 1.1 contre notre propre API et l’avons lancé avec Redocly CLI 2.62.1, sorti le 9 octobre. Le runner accepte les documents 1.1, et un workflow simple de création, lecture, modification et suppression passe. Deux des nouveautés passent le linter mais sont ignorées quand le workflow s’exécute vraiment. Cet article présente ce qui a changé, un workflow à télécharger et à lancer, et les deux nouveautés à éviter pour l’instant.
Ce que fait un fichier Arazzo
Un document Arazzo pointe vers une ou plusieurs descriptions d’API, généralement des fichiers OpenAPI, et liste des workflows. Chaque workflow est une liste d’étapes. Une étape désigne une opération par son operationId, remplit les paramètres et un corps de requête, dit ce qui compte comme un succès et garde des valeurs de la réponse pour les étapes suivantes.
Ce seul fichier sert à trois endroits :
- Tests. Un runner exécute les étapes contre un vrai serveur et échoue si un critère de succès n’est pas rempli. C’est ainsi que nous l’utilisons plus bas.
- Documentation. Le lecteur voit l’ordre des appels qu’exige une tâche, ce qu’un fichier OpenAPI ne peut pas exprimer.
- Agents d’IA. Un agent qui lit OpenAPI sait ce que fait chaque point d’accès, mais doit deviner comment les combiner. Un workflow Arazzo le lui dit.
Ce qui a changé dans Arazzo 1.1
La grande nouveauté est la prise en charge d’AsyncAPI, pour qu’un workflow mélange appels HTTP et canaux de messages. La plupart des autres changements facilitent l’écriture des workflows HTTP. Tout est listé dans l’annonce de la version :
| Changement | Ce qu’il fait |
|---|---|
Sources type: asyncapi | Une description source peut désormais être un document AsyncAPI, à côté d’openapi et d’arazzo. |
channelPath, action, correlationId, timeout | Des étapes qui envoient ou reçoivent des messages. action vaut send ou receive, correlationId associe une réponse à sa requête, et timeout est une limite en millisecondes. |
dependsOn | Une étape peut lister d’autres étapes qui doivent se terminer avant qu’elle commence. Lister une étape ne l’exécute pas. |
parameters sur les actions | Une action de succès ou d’échec qui appelle un autre workflow peut lui passer des entrées, par exemple pour renouveler un token puis réessayer. |
| Selector Object | Extrait une valeur d’une réponse ou d’une entrée avec JSONPath, XPath ou JSON Pointer. Fonctionne dans les outputs, les valeurs de paramètres et les corps de requête. |
| Expression Type Object | Fixe la version d’un langage de requête, comme JSONPath selon la RFC 9535 ou XPath 3.1. Remplace le Criterion Expression Type Object de la 1.0. |
in: querystring | Envoie un paramètre comme query string entière, à l’image du même ajout dans OpenAPI 3.2. |
$self | Une URI qui identifie le document, pour que les références vers lui ne dépendent pas de l’endroit où le fichier est stocké. |
La spécification a aussi gagné une grammaire complète pour les expressions d’exécution comme $steps.login.outputs.token, un ordre défini pour résoudre les descriptions sources et des règles exactes sur le moment où une condition est vraie. Elles n’ajoutent pas de champs, mais tranchent des cas où deux runners auraient pu diverger. La prise en charge de gRPC, GraphQL, MCP et des boucles figure sur la feuille de route des versions suivantes et n’est pas dans la 1.1.
Un workflow à lancer contre notre API
Ce workflow crée un produit sur api.testingapis.com, le relit, change son prix, le supprime et vérifie qu’il a disparu. Il n’utilise que des points d’accès gratuits, et tourne donc sans compte ni clé d’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.productIdTéléchargez-le avec le fichier OpenAPI vers lequel il pointe, mettez les deux dans un dossier et lancez-le. Node 22.12 ou plus récent suffit, puisque npx récupère le runner :
- products.arazzo.yaml
- testingapis-products.openapi.json (notre document OpenAPI en direct, réduit aux points d’accès produits et echo, avec une entrée
serverspour que le runner sache où envoyer les requêtes)
$ 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: 922msQuelques passages du fichier font plus qu’il n’y paraît :
$response.body#/data/idest une expression d’exécution avec un JSON Pointer après le#. Notre point d’accès de création enveloppe le nouveau produit dans{ message, data }, l’ID se trouve donc en/data/id. Chaque étape suivante le lit comme$steps.createProduct.outputs.productId.- Le critère de
readProductutilise le nouvel Expression Type Object pour dire que la requête est du JSONPath selon la RFC 9535. Une condition JSONPath passe quand le filtre trouve au moins un nœud, donc$[?@.category == 'electronics' && @.inStock == false]vérifie deux champs à la fois. Nous avons aussi inversé les valeurs attendues pour nous assurer que la vérification échoue quand il le faut, et elle a échoué. - La troisième vérification de chaque étape,
status code check, n’est pas dans le fichier Arazzo. Redocly l’ajoute à partir des réponses que liste le fichier OpenAPI, si bien qu’un code de statut non documenté par l’API fait échouer l’étape même si vos propres critères passent.
Le workflow fait le ménage quand une étape échoue
Les produits créés sur notre API sont partagés : quiconque liste les produits voit le vôtre jusqu’à sa suppression. Un test qui s’arrête à mi-chemin laisserait des déchets, donc readProduct et changePrice ont une action onFailure qui saute vers deleteProduct. Pour le vérifier, nous avons modifié le contrôle du prix pour qu’il ne puisse pas passer :
✗ 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 == 404Le workflow signale toujours un échec, mais le produit est supprimé avant. Faites de même dans tout workflow qui crée des données dans un environnement partagé.
Les Selector Objects passent le lint mais ne s’exécutent pas
Un Selector Object est la nouvelle façon d’extraire une valeur d’une réponse. Nous en avons utilisé un pour lire la note d’un produit avec un JSON Pointer et l’envoyer à notre point d’accès echo, qui renvoie ce qu’il reçoit :
- 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 valide ce fichier. Le point d’accès echo devrait recevoir {"rating": 4.3}. Voici ce qu’il a reçu :
{
"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"
}
}Le runner a résolu context en corps de réponse complet, puis a envoyé le Selector Object lui-même au lieu d’appliquer le sélecteur. Rien dans la sortie ne le signale. L’étape n’échoue que parce que nous avons vérifié ce que le serveur a reçu ; un workflow qui se contente de vérifier un 200 passerait en envoyant de mauvaises données.
En attendant que les runners suivent, utilisez la forme de la 1.0. Pour un JSON Pointer, elle est d’ailleurs plus courte :
outputs:
rating: $response.body#/rating/rateLes paramètres passés à un workflow se perdent
L’exemple de l’annonce pour parameters est une action d’échec qui appelle un workflow de renouvellement de token puis relance l’étape. Nos points d’accès gratuits ne délivrent pas de tokens, alors nous avons testé le même mécanisme avec un produit inexistant : l’action d’échec appelle un workflow report qui envoie son entrée reason au point d’accès 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 foundLe runner appelle bien le workflow report puis relance l’étape, dans l’ordre que décrit la spécification. Mais le point d’accès echo a reçu un objet vide :
"contentType": "application/json",
"contentLength": 2,
"body": "{}",
"json": {}reason n’est jamais arrivé. Lancer report seul avec --input reason=... fonctionne, le problème vient donc du passage de parameters depuis l’action. Pour un renouvellement de token, cela signifie que l’appel part sans refresh token.
Ce qui a tourné dans Redocly CLI 2.62.1
| Nouveauté d’Arazzo 1.1 | Lint | Exécution |
|---|---|---|
arazzo: 1.1.0 | Accepté | S’exécute |
$self | Accepté | Accepté ; rien à observer à l’exécution |
| Expression Type Object | Accepté | S’exécute, et échoue quand il le faut |
dependsOn | Accepté | Accepté ; les étapes s’exécutent de toute façon dans l’ordre |
| Selector Object | Accepté | Non appliqué : l’objet est envoyé comme donnée |
parameters sur les actions | Accepté | Le workflow s’exécute, les entrées sont vides |
| Étapes AsyncAPI | Non testé | Non testé |
Nous n’avons trouvé aucune entrée du changelog de Redocly qui annonce la prise en charge d’Arazzo 1.1 ; il s’agit donc peut-être simplement de fonctionnalités pas encore développées. Les autres outils listés dans le dépôt Arazzo peuvent se comporter autrement, et nous n’avons testé que Redocly.
Écrire des workflows 1.1 dès maintenant
- Utilisez
arazzo: 1.1.0et les nouveautés qui s’exécutent : Expression Type Objects,$selfetdependsOn. - Lisez les valeurs de réponse avec des expressions d’exécution comme
$response.body#/data/id, pas avec des Selector Objects. - Gérez le renouvellement de token en vous reconnectant dans une étape, pas via
parameterssur une action d’échec. - Vérifiez ce que le serveur a reçu, pas seulement le code de statut. Ni un lint réussi ni un
200n’ont repéré les problèmes de cet article. Notre point d’accès echo, si. - Fixez la version du runner, comme nous l’avons fait avec
@redocly/cli@2.62.1, pour qu’une mise à jour ne change pas vos résultats sans commit.
Si votre workflow teste un cycle de création, lecture, modification et suppression, notre leçon sur le test CRUD détaille les vérifications de chaque étape. Lire une spécification OpenAPI (en anglais) explique les valeurs operationId auxquelles se réfère un workflow, et la documentation de l’API de produits liste chaque champ que touche le workflow ci-dessus.