Arazzo 1.1:新機能と、今動くもの
2026 年 5 月 19 日、OpenAPI Initiative は Arazzo 1.1.0 をリリースしました。Arazzo は、一連の API 呼び出しを記述するための OpenAPI Initiative のフォーマットです。ログインし、 注文を作り、支払うといった流れを、各ステップが次のステップに値を渡しながら表します。OpenAPI は 1 つのエンドポイントが何を 受け取り何を返すかを表し、Arazzo はどのエンドポイントをどの順で呼び、各呼び出しが成功したかをどう判断するかを表します。
1.1 は、2024 年の 1.0 以来初めての機能追加リリースです。私たちは自分たちの API に対して 1.1 のワークフローを書き、10 月 9 日リリースの Redocly CLI 2.62.1 で実行しました。ランナーは 1.1 のドキュメントを受け付け、シンプルな作成・取得・更新・ 削除のワークフローは通ります。ただし新機能のうち 2 つは、リンターは通るのに実際の実行では無視されます。この記事では、 変更点、ダウンロードして実行できるワークフロー、そして当面は避けたほうがよい 2 つの機能を紹介します。
Arazzo ファイルがすること
Arazzo ドキュメントは 1 つ以上の API 記述(たいていは OpenAPI ファイル)を参照し、ワークフローを並べます。各ワークフローは ステップのリストです。ステップは operationId で操作を指定し、パラメータとリクエストボディを埋め、何を成功とみなすか を書き、後のステップのためにレスポンスから値を保存します。
この 1 つのファイルは 3 つの場面で役立ちます。
- テスト。ランナーが本物のサーバーに対してステップを実行し、成功条件を満たさなければ失敗します。この記事 ではこの使い方をします。
- ドキュメント。読み手は、ある作業に必要な呼び出しの順序を知ることができます。これは OpenAPI ファイル では表せません。
- AI エージェント。OpenAPI を読んだエージェントは各エンドポイントの役割はわかりますが、どう組み合わせる かは推測するしかありません。Arazzo のワークフローがそれを教えます。
Arazzo 1.1 の変更点
目玉は AsyncAPI への対応で、ワークフローの中で HTTP 呼び出しとメッセージチャネルを混在させられます。それ以外の変更の多くは、 HTTP ワークフローを書きやすくするものです。すべてリリースの告知に載っています。
| 変更 | 内容 |
|---|---|
type: asyncapi のソース | ソース記述として、openapi や arazzo に加えて AsyncAPI ドキュメントを使えるようになりました。 |
channelPath、action、correlationId、timeout | メッセージを送受信するステップ。action は send か receive、correlationId は応答をリクエストに対応づけ、timeout はミリ秒単位の上限です。 |
dependsOn | ステップが開始前に終わっている必要のある他のステップを挙げられます。挙げたステップが実行されるわけではありません。 |
アクションの parameters | 別のワークフローを呼ぶ成功・失敗アクションが、そのワークフローに入力を渡せます。たとえばトークンを更新してからリトライする場合です。 |
| Selector Object | JSONPath、XPath、JSON Pointer でレスポンスや入力から値を取り出します。outputs、パラメータ値、リクエストボディで使えます。 |
| Expression Type Object | RFC 9535 の JSONPath や XPath 3.1 のように、クエリ言語のバージョンを固定します。1.0 の Criterion Expression Type Object に代わるものです。 |
in: querystring | パラメータをクエリ文字列全体として送ります。OpenAPI 3.2 の同じ追加に合わせたものです。 |
$self | ドキュメントを識別する URI。ファイルの置き場所に関係なく参照できるようになります。 |
仕様には、$steps.login.outputs.token のようなランタイム式の完全な文法、ソース記述を解決する順序、条件が真になる場合の厳密なルールも加わりました。フィールドが 増えるわけではありませんが、2 つのランナーで結果が食い違いかねなかったケースに決着をつけます。gRPC、GraphQL、MCP、ループ への対応は今後のバージョンのロードマップにあり、1.1 には入っていません。
私たちの API で実行できるワークフロー
このワークフローは api.testingapis.com に商品を作り、読み直し、価格を変え、削除し、消えたことを確かめます。無料のエンドポイントだけを使うので、アカウントも 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.productId参照先の OpenAPI ファイルと一緒にダウンロードし、同じフォルダに置いて実行してください。npx がランナーを取ってくるので、Node 22.12 以降があれば十分です。
- products.arazzo.yaml
- testingapis-products.openapi.json(ライブの OpenAPI ドキュメントを商品と echo のエンドポイントに絞ったもので、ランナーがリクエストの送り先を知るための
serversエントリ付き)
$ 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: 922msファイルのいくつかの部分は、見た目以上の仕事をしています。
$response.body#/data/idは、#の後に JSON Pointer を付けたランタイム式です。私たちの作成エンドポイントは 新しい商品を{ message, data }で包むので、ID は/data/idにあります。後のステップはすべて$steps.createProduct.outputs.productIdとしてこれを読みます。readProductの条件では、新しい Expression Type Object を使ってクエリが RFC 9535 の JSONPath であることを示して います。JSONPath の条件はフィルターが 1 つ以上のノードに一致すれば通るので、$[?@.category == 'electronics' && @.inStock == false]で 2 つのフィールドを一度に確認できます。期待値を逆にして、失敗すべきときに失敗することも確かめました。ちゃんと失敗しました。- 各ステップの 3 つ目のチェック
status code checkは Arazzo ファイルにはありません。Redocly が OpenAPI ファイルに 載っているレスポンスから追加するもので、API が記述していないステータスコードが返ると、自分の条件が通ってもステップは 失敗します。
ステップが失敗したらワークフローが後片付けする
私たちの API で作った商品は共有されます。削除されるまで、商品を一覧した人には誰にでも見えます。途中で止まったテストはゴミ を残すので、readProduct と changePrice には deleteProduct に飛ぶ onFailure アクションを付けています。確認のため、価格のチェックを通らないように変えました。
✗ 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ワークフローは失敗を報告しますが、その前に商品は削除されています。共有環境にデータを作るワークフローでは、同じようにして ください。
Selector Object はリンターを通るが実行されない
Selector Object は、レスポンスから値を取り出す新しい方法です。JSON Pointer で商品の評価を読み、受け取ったものをそのまま 返す echo エンドポイントに送ってみました。
- 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 はこのファイルを通します。echo エンドポイントは {"rating": 4.3} を受け取るはずです。実際に届いたのはこれでした。
{
"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"
}
}ランナーは context をレスポンスボディ全体に解決し、セレクターを適用せずに Selector Object そのものを送りました。 出力には何の警告もありません。ステップが失敗するのは、サーバーが受け取った内容を私たちが確認したからにすぎません。200 しか確認しないワークフローなら、間違ったデータを送りながら通ってしまいます。
ランナーが追いつくまでは 1.0 の書き方を使いましょう。JSON Pointer なら、そのほうが短くもなります。
outputs:
rating: $response.body#/rating/rateワークフローに渡したパラメータが消える
告知で parameters の例として挙げられているのは、トークン更新のワークフローを呼んでからステップをリトライする失敗アクションです。私たちの無料 エンドポイントはトークンを発行しないので、存在しない商品で同じ仕組みを試しました。失敗アクションが report ワークフローを呼び、そのワークフローが入力の reason を 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 foundランナーは仕様どおりの順で report ワークフローを呼び、ステップをリトライします。しかし echo エンドポイントが受け取った のは空のオブジェクトでした。
"contentType": "application/json",
"contentLength": 2,
"body": "{}",
"json": {}reason は届きませんでした。report を単独で --input reason=... 付きで実行すると動くので、問題はアクションから parameters を渡すところにあります。トークン更新なら、更新の呼び出しが refresh token なしで送られることになります。
Redocly CLI 2.62.1 で動いたもの
| Arazzo 1.1 の機能 | Lint | 実行 |
|---|---|---|
arazzo: 1.1.0 | 受け付ける | 動く |
$self | 受け付ける | 受け付ける。実行時に観察できることはない |
| Expression Type Object | 受け付ける | 動き、失敗すべきときに失敗する |
dependsOn | 受け付ける | 受け付ける。ステップはどのみち順に実行される |
| Selector Object | 受け付ける | 適用されない。オブジェクトがデータとして送られる |
アクションの parameters | 受け付ける | ワークフローは動くが、入力が空 |
| AsyncAPI のステップ | 未検証 | 未検証 |
Arazzo 1.1 への対応をうたう Redocly の changelog の項目は見つからなかったので、単にまだ作られていない機能なのかもしれません。Arazzo のリポジトリに載っている他のツールは違う動きをする可能性があり、私たちが試したのは Redocly だけです。
今 1.1 のワークフローを書くなら
arazzo: 1.1.0と、実際に動く機能(Expression Type Object、$self、dependsOn)を使う。- レスポンスの値は Selector Object ではなく、
$response.body#/data/idのようなランタイム式で読む。 - トークンの更新は、失敗アクションの
parametersではなく、ステップの中でログインし直して扱う。 - ステータスコードだけでなく、サーバーが受け取った内容を確認する。この記事の問題は、通った lint も
200も見つけられませんでした。見つけたのは echo エンドポイントです。 @redocly/cli@2.62.1のようにランナーのバージョンを固定し、ランナーの更新でコミットなしに結果が変わらないようにする。
ワークフローで作成・取得・更新・削除のサイクルをテストするなら、CRUD テストのレッスンで各ステップに必要なチェックを説明しています。OpenAPI 仕様の読み方(英語)ではワークフローが参照する operationId を説明し、商品 API のドキュメントには上のワークフローが触れるすべてのフィールドが載っています。