Source: https://www.openapis.org/arazzo
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.
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:
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.
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:
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:
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:
Example Workflow with One Step
Below is an example of a simple workflow that contains only one step:
1workflows:2- workflowId: ApplyForLoanAtCheckout3 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: object7 required:8 - products9 properties:10 products:11 type: array12 minItems: 113 items:14 type: object15 required:16 - productCode17 properties:18 productCode:19 description: Product code for loan application.20 type: string21 steps:22 - stepId: checkLoanCanBeProvided23 description: |24 Call the BNPL API to filter the basket for products qualifying for checkout25 operationId: findEligibleProducts26 requestBody:27 contentType: application/json28 payload: |29 {30 "products": "{$inputs.products}"31 }32 successCriteria:33 - condition: $statusCode == 20034 onSuccess:35 - name: existingCustomerNotEligible36 type: end37 criteria:38 - condition: $statusCode == 20039 - condition: $response.body#/existingCustomerNotEligible == false40 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.
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:
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.02info:3 title: BNPL Workflow description4 description: >-5 Describes the steps to secure a loan at checkout from a BNPL platform.6 version: 1.0.07sourceDescriptions:8 - name: BnplApi9 url: https://raw.githubusercontent.com/OAI/Arazzo-Specification/main/examples/1.0.0/bnpl-openapi.yaml10 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: ApplyForLoanAtCheckout3 summary: Apply for a loan at checkout using a BNPL platform4 description: Describes the steps to secure a loan at checkout from a BNPL platform.5 inputs:6 type: object7 required:8 - customer9 - products10 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.
12 steps:3 - stepId: checkLoanCanBeProvided4 description: Call the BNPL API to filter the basket for products.5 operationId: findEligibleProducts6 requestBody:7 contentType: application/json8 payload: |9 {10 "customer": "{$inputs.customer}",11 "products": "{$inputs.products}"12 }13 successCriteria:14 - condition: $statusCode == 20015 onSuccess:16 - name: existingCustomerNotEligible17 type: end18 criteria:19 - condition: $statusCode == 20020 - condition: $response.body#/existingCustomerNotEligible == false21 - name: qualifyingProductsFound22 type: goto23 stepId: getCustomerTermsAndConditions24 criteria:25 - condition: $statusCode == 20026 - context: $response.body27 type: jsonpath28 condition: $[?count(@.products) > 0]29 - name: qualifyingProductsNotFound30 type: end31 criteria:32 - condition: $statusCode == 20033 - context: $response.body34 type: jsonpath35 condition: $[?count(@.products) == 0]36 outputs:37 eligibilityCheckRequired: $response.body#/eligibilityCheckRequired38 eligibleProducts: $response.body#/products39 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.
12 - stepId: getCustomerTermsAndConditions3 description: Get the terms and conditions for the BNPL loans.4 operationId: getTermsAndConditions5 successCriteria:6 - condition: $statusCode == 2007 onSuccess:8 - name: eligibilityCheckRequired9 type: goto10 stepId: createCustomer11 criteria:12 - condition: $steps.checkLoanCanBeProvided.outputs.eligibilityCheckRequired == true13 - name: eligibilityCheckNotRequired14 type: goto15 stepId: initiateBnplTransaction16 criteria:17 - condition: $steps.checkLoanCanBeProvided.outputs.eligibilityCheckRequired == false18 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.
12 - stepId: createCustomer3 description: |4 Call the BNPL platform and verify the customer is eligible5 for the loan, which creates a customer resource.6 operationId: createCustomer7 requestBody:8 contentType: application/json9 payload: |10 {11 "firstName": "{$inputs.customer.firstName}",12 "lastName": "{$inputs.customer.lastName}",13 "dateOfBirth": "{$inputs.customer.dateOfBirth}",14 "postalCode": "{$inputs.customer.postalCode}"15 "termsAndConditionsAccepted": true16 }17 successCriteria:18 - condition: $statusCode == 200 || $statusCode == 20119 onSuccess:20 - name: customerIsEligible21 type: goto22 stepId: initiateBnplTransaction23 criteria:24 - condition: $statusCode == 20125 - name: customerIsNotEligible26 type: end27 criteria:28 - condition: $statusCode == 20029 outputs:30 customer: $response.body#/links/self
Step 5: Define the Fourth Step: initiateBnplTransaction
12 - stepId: initiateBnplTransaction3 description: Initiate the BNPL transaction4 operationId: createBnplTransaction5 requestBody:6 contentType: application/json7 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 == 20215 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.
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 main23import (4 "context"5 "fmt"6 "os"78 "github.com/speakeasy-api/openapi/arazzo"9)1011func main() {12 ctx := context.Background()1314 f, err := os.Open("arazzo.yaml")15 if err != nil {16 panic(err)17 }1819 _, validationErrs, err := arazzo.Unmarshal(ctx, f)20 if err != nil {21 panic(err)22 }2324 for _, err := range validationErrs {25 fmt.Printf("%s", err.Error())26 }}
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:
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:
Execute end-to-end tests from an Arazzo workflow API description document
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.