What is Arazzo Workflow Specification ?

OpenAPI specification has become the standard for ensuring that both humans and machines can understand and interact with APIs. However, in most cases, achieving a specific outcome requires a series of orchestrated API calls, rather than a single request.

While the OpenAPI specification excels at defining individual API endpoints and parameters, it is not in its scope to describe the workflows and business logic that govern these API calls. This is where Arazzo comes in. Arazzo provides a standardized way to define and document API workflows, enabling developers to create more complex and integrated API-based systems.

In today's tutorial we will learn Arazzo and explore the available tooling to validate and use Arazzo documents.

Introduction to OpenApi Arazzo

Arazzo is a language-agnostic specification initiated by the OpenAPI Initiative. It aims to define sequences of calls to one or multiple API endpoints. To better understand this specification and its uses, let's consider a scenario where we have two APIs for creating cloud interconnection links for example, the MegaPort API and the Equinix API and an application that use these two APIs, when a user request a new link between different cloud providers, the application will create a redundant interconnections behind the scenes.

The worklfow that will be executed by the application is similar to this one:

  • Get data from user inputs for example: the source and destination cloud provider...
  • Send login request to Megaport API
  • Get authentification token from the body
  • List the available ports using the authentification token
  • Create interconnection and return the interconnection ID
  • List the available ports in Equinix API using an APIKEY
  • Create interconnection and return the interconnection ID
  • Return the two interconnection IDs

To better document this workflow both for human and tools, we can define an Arazzo workflow in JSON or YAML documents. These documents specify the required inputs to execute the workflow, the requests to send, and the expected outputs from the workflow. They also define how outputs from one step can be used as inputs for subsequent steps.

The Arazzo definition document offers many benefits. For example, it enables AI agents to execute the workflow in a deterministic manner and facilitates the generation of end-to-end test scenarios, rather than testing each API separately.

Use Cases

Arazzo is not limited to AI agents and end-to-end testing. It serves a variety of use cases outlined in the official documentation. These use cases include, but are not limited to:

  • Interactive living workflow documentation
  • Automated documentation generation (e.g. Developer Portal documentation)
  • Code and SDK generation driven by functional use cases
  • Automation of test cases
  • Automated regulatory compliance checks
  • Deterministic API invocation by AI-based LLMs

Arazzo Structure

Arazzo's fundamental idea is to create a workflow definition, containing a series of API calls that work together to provide a valuable outcome for users. In this definition, you should specify the Arazzo version, info about the workflows, sourceDescriptions (Contains the OpenAPI documents) and list of workflows.

The schema below show the basic structure of an Arazzo document:

Arazzo document structure

Source: Arazzo Github repository

As illustrated in the image, a typical workflow contain several components: inputs and parameters, success actions, failure actions, and outputs. Each workflow must contain at least one step. These steps, in turn, have their own set of parameters, success criteria, success actions, failure actions, and outputs.

Breaking Down Workflow Steps

To further clarify, let's examine the components of a workflow step:

  • Parameters: Define the inputs for the step
  • Success criteria: Outline the conditions for the step to be considered successful
  • Success actions: Specify the actions to be taken when the step is successful
  • Failure actions: Define the actions to be taken when the step fails
  • Outputs: Determine the results or outcomes of the step

Example Workflow with One Step

Below is an example of a simple workflow that contains only one step:

1workflows:
2- workflowId: ApplyForLoanAtCheckout
3 summary: Apply for a loan at checkout using a BNPL platform.
4 description: Describes the steps to secure a loan at checkout.
5 inputs:
6 type: object
7 required:
8 - products
9 properties:
10 products:
11 type: array
12 minItems: 1
13 items:
14 type: object
15 required:
16 - productCode
17 properties:
18 productCode:
19 description: Product code for loan application.
20 type: string
21 steps:
22 - stepId: checkLoanCanBeProvided
23 description: |
24 Call the BNPL API to filter the basket for products qualifying for checkout
25 operationId: findEligibleProducts
26 requestBody:
27 contentType: application/json
28 payload: |
29 {
30 "products": "{$inputs.products}"
31 }
32 successCriteria:
33 - condition: $statusCode == 200
34 onSuccess:
35 - name: existingCustomerNotEligible
36 type: end
37 criteria:
38 - condition: $statusCode == 200
39 - condition: $response.body#/existingCustomerNotEligible == false
40 outputs:
41 existingCustomerNotEligible: $response.body#/existingCustomerNotEligible

The workflow must have a unique ID; in our case, it's ApplyForLoanAtCheckout. The workflow takes a list of products as inputs and contains one step, checkLoanCanBeProvided. The step has an operationId, findEligibleProducts, which references an operationId of an OpenAPI document that we have to declare first in sourceDescriptions. The requestBody (our OpenAPI operationId is a POST request). The successCriteria is the criteria that will evaluate if our step is successful or not; in our case, we check for the status code of the request - if it's 200, our step is successful. OnSuccess defines what we should do if the step was successful. Here, we have the type 'end', which means that if the criteria are matched, we end the workflow.

Create a Arazzo Document Step by Step

In this section, we will create an Arazzo definition for a modified version of the 'Buy now, pay later' workflow, taken from the GitHub repository of the OpenAPI Initiative.. It's is available at https://github.com/OAI/Arazzo-Specification/blob/main/examples/1.0.0/bnpl-openapi.yaml

The diagram below show the workflow that we will define with our Arazzo document:

Buy Now Pay Later workflow

Image created by the author

The first step is checkLoanCanBeProvided, if we don't get qualifying products the workflow will end, otherwise we move to the next step which is getTermsAndConditions. We check the result of this step if the eligibilityCheckRequired is True we move to the step createCustomer else we move to initiateBnplTransaction.

Step 1: Create the Header of the Document

In the first step, we define the Arazzo version, information about our document, and specify sourceDescriptions, which contains the list of APIs that our workflows will use. Here, we only use one API, defined in bnpl-openapi.yaml.

1arazzo: 1.0.0
2info:
3 title: BNPL Workflow description
4 description: >-
5 Describes the steps to secure a loan at checkout from a BNPL platform.
6 version: 1.0.0
7sourceDescriptions:
8 - name: BnplApi
9 url: https://raw.githubusercontent.com/OAI/Arazzo-Specification/main/examples/1.0.0/bnpl-openapi.yaml
10 type: openapi

Step 2: Define the Worklow and it's Inputs

The next step we create the workflowId, description of the workflow, and the list of required inputs to execute it. In our example, the workflow take customer and products as input.

1workflows:
2- workflowId: ApplyForLoanAtCheckout
3 summary: Apply for a loan at checkout using a BNPL platform
4 description: Describes the steps to secure a loan at checkout from a BNPL platform.
5 inputs:
6 type: object
7 required:
8 - customer
9 - products
10 properties:
11 customer:...
12 products:...

Step 3: Define the First Step

The first step has 3 actions: the actions existingCustomerNotEligible and qualifyingProductsNotFound will end the workflow if we have existingCustomerNotEligible=False in the body response or the number of returned products equals 0. The other success action will proceed with the workflow if the two criteria are met: the status code must be 200 OK and the number of products is greater than 0. This action has a type 'goto' and a reference to a stepId. The step referenced here (getCustomerTermsAndConditions) will be launched next.

1
2 steps:
3 - stepId: checkLoanCanBeProvided
4 description: Call the BNPL API to filter the basket for products.
5 operationId: findEligibleProducts
6 requestBody:
7 contentType: application/json
8 payload: |
9 {
10 "customer": "{$inputs.customer}",
11 "products": "{$inputs.products}"
12 }
13 successCriteria:
14 - condition: $statusCode == 200
15 onSuccess:
16 - name: existingCustomerNotEligible
17 type: end
18 criteria:
19 - condition: $statusCode == 200
20 - condition: $response.body#/existingCustomerNotEligible == false
21 - name: qualifyingProductsFound
22 type: goto
23 stepId: getCustomerTermsAndConditions
24 criteria:
25 - condition: $statusCode == 200
26 - context: $response.body
27 type: jsonpath
28 condition: $[?count(@.products) > 0]
29 - name: qualifyingProductsNotFound
30 type: end
31 criteria:
32 - condition: $statusCode == 200
33 - context: $response.body
34 type: jsonpath
35 condition: $[?count(@.products) == 0]
36 outputs:
37 eligibilityCheckRequired: $response.body#/eligibilityCheckRequired
38 eligibleProducts: $response.body#/products
39 totalLoanAmount: $response.body#/totalAmount

Step 4: Define the Second Step: getCustomerTermsAndConditions

After creating the first step, let's move on to the getCustomerTermsAndConditions step. This is a simple step that references the getTermsAndConditions operationId, which is a GET request. It checks for the eligibilityCheckRequired parameter, which is returned from the previous step. Depending on the value of the parameter, it will jump to the next step and return the terms and conditions as output.

1
2 - stepId: getCustomerTermsAndConditions
3 description: Get the terms and conditions for the BNPL loans.
4 operationId: getTermsAndConditions
5 successCriteria:
6 - condition: $statusCode == 200
7 onSuccess:
8 - name: eligibilityCheckRequired
9 type: goto
10 stepId: createCustomer
11 criteria:
12 - condition: $steps.checkLoanCanBeProvided.outputs.eligibilityCheckRequired == true
13 - name: eligibilityCheckNotRequired
14 type: goto
15 stepId: initiateBnplTransaction
16 criteria:
17 - condition: $steps.checkLoanCanBeProvided.outputs.eligibilityCheckRequired == false
18 outputs:
19 termsAndConditions: $response.body

Step 5: Define the Third Step: createCustomer

This step will create the customer with a POST request. If the status code is 200 OK it move to the final step otherwise it end the workflow.

1
2 - stepId: createCustomer
3 description: |
4 Call the BNPL platform and verify the customer is eligible
5 for the loan, which creates a customer resource.
6 operationId: createCustomer
7 requestBody:
8 contentType: application/json
9 payload: |
10 {
11 "firstName": "{$inputs.customer.firstName}",
12 "lastName": "{$inputs.customer.lastName}",
13 "dateOfBirth": "{$inputs.customer.dateOfBirth}",
14 "postalCode": "{$inputs.customer.postalCode}"
15 "termsAndConditionsAccepted": true
16 }
17 successCriteria:
18 - condition: $statusCode == 200 || $statusCode == 201
19 onSuccess:
20 - name: customerIsEligible
21 type: goto
22 stepId: initiateBnplTransaction
23 criteria:
24 - condition: $statusCode == 201
25 - name: customerIsNotEligible
26 type: end
27 criteria:
28 - condition: $statusCode == 200
29 outputs:
30 customer: $response.body#/links/self

Step 5: Define the Fourth Step: initiateBnplTransaction

1
2 - stepId: initiateBnplTransaction
3 description: Initiate the BNPL transaction
4 operationId: createBnplTransaction
5 requestBody:
6 contentType: application/json
7 payload: |
8 {
9 "enrolledCustomer": "{$inputs.customer.uri}",
10 "newCustomer": "{$steps.createCustomer.outputs.customer}",
11 "products": "{$steps.checkLoanCanBeProvided.outputs.eligibleProducts}"
12 }
13 successCriteria:
14 - condition: $statusCode == 202
15 outputs:
16 loanTransactionId: $response.body#/loanTransactionId

The Last Step: Define the Outputs of the Workflow

In Arazzo, the step output is only available within the context of the workflow, not outside of it. So, if we want an output to be available for other workflows, we should declare it in the output of the workflow, as we did for the loanTransactionId output.

1outputs:
2 loanTransactionId: $steps.initiateBnplTransaction.outputs.loanTransactionId

That's it, we have manually created an Arazzo document. If you think that this process is time-consuming, there is a GPT created by SmartBear available in ChatGPT. You can use it to create an Arazzo document much faster.

Validation of Arazzo workflows

After creating our Arazzo document, we should validate that it adheres to the specified standards. Several tools are available to lint and verify the validity of an Arazzo document. Below is a guide on how to perform this validation using Spectral, Redocly, and Speakeasy.

Using Spectral:

Spectral is an open-source linting tool that supports the validation of JSON and YAML files. As a generic linter, it requires a ruleset to lint and validate documents.

To install Spectral, use the following command:

1yarn global add @stoplight/spectral-cli

To validate our Arazzo document we should create a .spectral.yaml file, as follow:

1extends:
2 - "spectral:arazzo"

And the last step, run the linter using teh following command:

1spectral lint your-arazzo-file.yaml --ruleset=arazzo-ruleset.yaml

If you have a ruleset file in the same directory as the documents you are linting you can omit the ruleset config.

Using Redocly:

To validate Arazzo documents using Redocly, no specific setup is required. You only need to run the following command:

1npx @redocly/cli lint my-arazzo.yaml"

Using Arazzo parser from Speakeasy:

With Golang, you can validate your Arazzo documents using a package provided by Speakeasy. Below is an example of how to validate an Arazzo.yaml file:

1package main
2
3import (
4 "context"
5 "fmt"
6 "os"
7
8 "github.com/speakeasy-api/openapi/arazzo"
9)
10
11func main() {
12 ctx := context.Background()
13
14 f, err := os.Open("arazzo.yaml")
15 if err != nil {
16 panic(err)
17 }
18
19 _, validationErrs, err := arazzo.Unmarshal(ctx, f)
20 if err != nil {
21 panic(err)
22 }
23
24 for _, err := range validationErrs {
25 fmt.Printf("%s", err.Error())
26 }}

Execution of Arazzo workflows

After creating and validating our Arazzo document, how can we benefit from it ?

We can use the Arazzo description to automatically generate SDKs for APIs or leverage AI agents to execute complex workflows. Additionally, we could develop a tool that reads an Arazzo document and generates an executable workflow in a preferred programming language or an existing workflow orchestrator like Airflow or Prefect .

Example steps to use our Arazzo document:

  • Validate the Arazzo document.
  • Generate client code to orchestrate the API calls using the workflow.
  • Execute the generated client code in the target environment.

Testing API workflows:

One of the use cases for Arazzo is the implementation of tests for API workflows. In this section, we'll explore how to create an end-to-end test starting from an Arazzo document and using Respect tool.

Respect executes the workflow, by calling the endpoints with the provided inputs. We will need the Arazzo document and the openapi API spec. I wil be using the example provided by Redocly to do that.

1npx @redocly/cli respect your-arazzo-spec.yaml --input token=abc123 --verbose

After executing this command you will see an output similar to the following:

Screenshot execute end-to-end tests from an Arazzo workflow API description document

Execute end-to-end tests from an Arazzo workflow API description document

Wrapping Up

The OpenAPI community has introduced a new specification that enables both humans and machines to understand how a specific workflow or process should be executed. This breakthrough allows us to automate numerous tasks that were previously impossible without such a specification. With the rise of API adoption, automation, and AI agents, the Arazzo specification is poised to see widespread adoption in the coming years.