Arazzo 1.1: was neu ist und was heute läuft
Am 19. Mai 2026 hat die OpenAPI Initiative Arazzo 1.1.0 veröffentlicht. Arazzo ist das Format der OpenAPI Initiative, um eine Folge von API-Aufrufen zu beschreiben: anmelden, dann eine Bestellung anlegen, dann bezahlen, wobei jeder Schritt Werte an den nächsten weitergibt. OpenAPI sagt, was ein Endpunkt annimmt und zurückgibt. Arazzo sagt, welche Endpunkte in welcher Reihenfolge aufzurufen sind und woran man erkennt, ob ein Aufruf geklappt hat.
Version 1.1 ist das erste Feature-Release seit 1.0 im Jahr 2024. Wir haben einen 1.1-Workflow gegen unsere eigene API geschrieben und mit Redocly CLI 2.62.1 ausgeführt, erschienen am 9. Oktober. Der Runner akzeptiert 1.1-Dokumente, und ein einfacher Workflow aus Anlegen, Lesen, Ändern und Löschen besteht. Zwei der Neuerungen bestehen den Linter, werden beim echten Ausführen aber ignoriert. Dieser Beitrag zeigt, was sich geändert hat, einen Workflow zum Herunterladen und Ausführen und die zwei Neuerungen, die du vorerst meiden solltest.
Was eine Arazzo-Datei tut
Ein Arazzo-Dokument verweist auf eine oder mehrere API-Beschreibungen, meist OpenAPI-Dateien, und listet Workflows auf. Jeder Workflow ist eine Liste von Schritten. Ein Schritt nennt eine Operation über ihre operationId, füllt Parameter und einen Request-Body, legt fest, was als Erfolg zählt, und speichert Werte aus der Antwort für spätere Schritte.
Diese eine Datei ist an drei Stellen nützlich:
- Tests. Ein Runner führt die Schritte gegen einen echten Server aus und schlägt fehl, wenn ein Erfolgskriterium nicht erfüllt ist. So nutzen wir es unten.
- Dokumentation. Lesende sehen die Reihenfolge der Aufrufe, die eine Aufgabe braucht, was eine OpenAPI-Datei nicht ausdrücken kann.
- KI-Agenten. Ein Agent, der OpenAPI liest, weiß, was jeder Endpunkt tut, muss aber raten, wie er sie kombiniert. Ein Arazzo-Workflow sagt es ihm.
Was sich in Arazzo 1.1 geändert hat
Die große Neuerung ist AsyncAPI-Unterstützung, damit ein Workflow HTTP-Aufrufe mit Nachrichtenkanälen mischen kann. Die meisten anderen Änderungen machen HTTP-Workflows leichter zu schreiben. Alle stehen in der Ankündigung:
| Änderung | Was sie tut |
|---|---|
Quellen mit type: asyncapi | Eine Quellbeschreibung kann jetzt ein AsyncAPI-Dokument sein, neben openapi und arazzo. |
channelPath, action, correlationId, timeout | Schritte, die Nachrichten senden oder empfangen. action ist send oder receive, correlationId ordnet eine Antwort ihrer Anfrage zu, und timeout ist ein Limit in Millisekunden. |
dependsOn | Ein Schritt kann andere Schritte nennen, die fertig sein müssen, bevor er startet. Einen Schritt zu nennen führt ihn nicht aus. |
parameters bei Actions | Eine Erfolgs- oder Fehler-Action, die einen anderen Workflow aufruft, kann ihm Eingaben übergeben, etwa um ein Token zu erneuern und dann neu zu versuchen. |
| Selector Object | Holt einen Wert aus einer Antwort oder Eingabe, mit JSONPath, XPath oder JSON Pointer. Funktioniert in Outputs, Parameterwerten und Request-Bodies. |
| Expression Type Object | Legt die Version einer Abfragesprache fest, etwa JSONPath nach RFC 9535 oder XPath 3.1. Ersetzt das Criterion Expression Type Object aus 1.0. |
in: querystring | Sendet einen Parameter als gesamten Query-String, passend zur gleichen Neuerung in OpenAPI 3.2. |
$self | Eine URI, die das Dokument identifiziert, damit Verweise darauf nicht davon abhängen, wo die Datei gerade liegt. |
Die Spezifikation hat außerdem eine vollständige Grammatik für Runtime-Ausdrücke wie $steps.login.outputs.token bekommen, eine festgelegte Reihenfolge zum Auflösen der Quellbeschreibungen und genaue Regeln dafür, wann eine Bedingung als wahr gilt. Neue Felder kommen damit nicht hinzu, aber sie klären Fälle, in denen zwei Runner unterschiedlicher Meinung sein könnten. Unterstützung für gRPC, GraphQL, MCP und Schleifen steht auf der Roadmap für spätere Versionen und ist nicht in 1.1.
Ein Workflow, den du gegen unsere API ausführen kannst
Dieser Workflow legt auf api.testingapis.com ein Produkt an, liest es zurück, ändert seinen Preis, löscht es und prüft, dass es weg ist. Er nutzt nur kostenlose Endpunkte und läuft daher ohne Konto oder API-Schlüssel.
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.productIdLade ihn zusammen mit der OpenAPI-Datei herunter, auf die er verweist, leg beide in einen Ordner und führ ihn aus. Node 22.12 oder neuer reicht, denn npx holt den Runner:
- products.arazzo.yaml
- testingapis-products.openapi.json (unser Live-OpenAPI-Dokument, gekürzt auf die Produkt- und Echo-Endpunkte, mit einem
servers-Eintrag, damit der Runner weiß, wohin er Requests schicken soll)
$ 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: 922msEin paar Stellen der Datei leisten mehr, als man ihnen ansieht:
$response.body#/data/idist ein Runtime-Ausdruck mit einem JSON Pointer nach dem#. Unser Anlege-Endpunkt verpackt das neue Produkt in{ message, data }, die ID steht also unter/data/id. Jeder spätere Schritt liest sie als$steps.createProduct.outputs.productId.- Das Kriterium in
readProductnutzt das neue Expression Type Object, um festzulegen, dass die Abfrage JSONPath nach RFC 9535 ist. Eine JSONPath-Bedingung ist erfüllt, wenn der Filter mindestens einen Knoten trifft,$[?@.category == 'electronics' && @.inStock == false]prüft also zwei Felder auf einmal. Wir haben auch die erwarteten Werte umgedreht, um sicherzugehen, dass die Prüfung scheitert, wenn sie soll, und das tat sie. - Die dritte Prüfung jedes Schritts,
status code check, steht nicht in der Arazzo-Datei. Redocly fügt sie aus den Antworten hinzu, die die OpenAPI-Datei auflistet, ein Statuscode, den die API nicht dokumentiert, lässt den Schritt also scheitern, selbst wenn deine eigenen Kriterien erfüllt sind.
Der Workflow räumt auf, wenn ein Schritt scheitert
Auf unserer API angelegte Produkte sind geteilt: Wer Produkte auflistet, sieht deines, bis es gelöscht ist. Ein Test, der auf halbem Weg stehen bleibt, würde Müll hinterlassen, deshalb haben readProduct und changePrice eine onFailure-Action, die zu deleteProduct springt. Zur Kontrolle haben wir die Preisprüfung so geändert, dass sie nicht bestehen konnte:
✗ 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 == 404Der Workflow meldet weiterhin einen Fehler, aber zuerst wird das Produkt gelöscht. Mach es in jedem Workflow so, der in einer geteilten Umgebung Daten anlegt.
Selector Objects bestehen den Lint, laufen aber nicht
Ein Selector Object ist der neue Weg, einen Wert aus einer Antwort zu holen. Wir haben damit die Bewertung eines Produkts per JSON Pointer gelesen und an unseren Echo-Endpunkt geschickt, der zurückgibt, was er empfängt:
- 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 lässt diese Datei durch. Der Echo-Endpunkt sollte {"rating": 4.3} empfangen. Das kam an:
{
"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"
}
}Der Runner hat context zum ganzen Antwort-Body aufgelöst und dann das Selector Object selbst gesendet, statt den Selektor anzuwenden. Nichts in der Ausgabe warnt davor. Der Schritt scheitert nur, weil wir geprüft haben, was der Server empfangen hat; ein Workflow, der nur auf ein 200 prüft, würde bestehen und dabei falsche Daten senden.
Bis die Runner nachziehen, nimm die Form aus 1.0. Für einen JSON Pointer ist sie ohnehin kürzer:
outputs:
rating: $response.body#/rating/rateAn einen Workflow übergebene Parameter gehen verloren
Das Beispiel der Ankündigung für parameters ist eine Fehler-Action, die einen Workflow zur Token-Erneuerung aufruft und den Schritt wiederholt. Unsere kostenlosen Endpunkte geben keine Tokens aus, also haben wir denselben Mechanismus mit einem fehlenden Produkt getestet: Die Fehler-Action ruft einen Workflow report auf, der seine Eingabe reason an den Echo-Endpunkt schickt.
- 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 foundDer Runner ruft den Workflow report auf und wiederholt dann den Schritt, in der Reihenfolge, die die Spezifikation beschreibt. Aber der Echo-Endpunkt hat ein leeres Objekt bekommen:
"contentType": "application/json",
"contentLength": 2,
"body": "{}",
"json": {}reason kam nie an. report allein mit --input reason=... auszuführen funktioniert, das Problem liegt also im Übergeben von parameters aus der Action. Bei einer Token-Erneuerung heißt das: Der Aufruf geht ohne Refresh-Token raus.
Was in Redocly CLI 2.62.1 lief
| Neuerung in Arazzo 1.1 | Lint | Ausführung |
|---|---|---|
arazzo: 1.1.0 | Akzeptiert | Läuft |
$self | Akzeptiert | Akzeptiert; zur Laufzeit gibt es nichts zu beobachten |
| Expression Type Object | Akzeptiert | Läuft, und scheitert, wenn es soll |
dependsOn | Akzeptiert | Akzeptiert; die Schritte laufen ohnehin der Reihe nach |
| Selector Object | Akzeptiert | Nicht angewandt: das Objekt wird als Daten gesendet |
parameters bei Actions | Akzeptiert | Workflow läuft, Eingaben sind leer |
| AsyncAPI-Schritte | Nicht getestet | Nicht getestet |
Wir haben keinen Eintrag im Changelog von Redocly gefunden, der Arazzo-1.1-Unterstützung verspricht, es sind also womöglich einfach Funktionen, die noch nicht gebaut sind. Die anderen Tools im Arazzo-Repository verhalten sich vielleicht anders, und wir haben nur Redocly getestet.
1.1-Workflows heute schreiben
- Nutze
arazzo: 1.1.0und die Neuerungen, die laufen: Expression Type Objects,$selfunddependsOn. - Lies Werte aus der Antwort mit Runtime-Ausdrücken wie
$response.body#/data/id, nicht mit Selector Objects. - Löse die Token-Erneuerung, indem du dich in einem Schritt neu anmeldest, nicht über
parametersan einer Fehler-Action. - Prüfe, was der Server empfangen hat, nicht nur den Statuscode. Weder ein bestandener Lint noch ein
200haben eines der Probleme in diesem Beitrag gefunden. Unser Echo-Endpunkt schon. - Fixiere die Runner-Version, wie wir es mit
@redocly/cli@2.62.1getan haben, damit ein Update deine Ergebnisse nicht ohne Commit ändern kann.
Testet dein Workflow einen Zyklus aus Anlegen, Lesen, Ändern und Löschen, behandelt unsere Lektion zum CRUD-Testing, welche Prüfungen jeder Schritt braucht. Eine OpenAPI-Spezifikation lesen (auf Englisch) erklärt die Werte von operationId, auf die ein Workflow verweist, und die Doku zur Produkt-API listet jedes Feld, das der Workflow oben berührt.