Arazzo 1.1: o que há de novo e o que roda hoje

Compartilhar

Em 19 de maio de 2026 a OpenAPI Initiative lançou o Arazzo 1.1.0. Arazzo é o formato da OpenAPI Initiative para descrever uma sequência de chamadas de API: fazer login, depois criar um pedido, depois pagar, com cada passo passando valores para o próximo. O OpenAPI diz o que um endpoint aceita e devolve. O Arazzo diz quais endpoints chamar, em que ordem, e como saber se cada chamada deu certo.

A versão 1.1 é a primeira com novidades desde a 1.0, de 2024. Escrevemos um workflow 1.1 contra a nossa própria API e rodamos com o Redocly CLI 2.62.1, lançado em 9 de outubro. O runner aceita documentos 1.1, e um workflow simples de criar, ler, atualizar e apagar passa. Duas das novidades passam no linter mas são ignoradas quando o workflow roda de verdade. Este post mostra o que mudou, traz um workflow que você pode baixar e rodar, e aponta as duas novidades que é melhor evitar por enquanto.

O que um arquivo Arazzo faz

Um documento Arazzo aponta para uma ou mais descrições de API, normalmente arquivos OpenAPI, e lista workflows. Cada workflow é uma lista de passos. Um passo nomeia uma operação pelo seu operationId, preenche parâmetros e um corpo de requisição, diz o que conta como sucesso e guarda valores da resposta para os passos seguintes.

Esse único arquivo é útil em três lugares:

  • Testes. Um runner executa os passos contra um servidor real e falha se um critério de sucesso não for atendido. É assim que usamos abaixo.
  • Documentação. Quem lê vê a ordem de chamadas que uma tarefa exige, algo que um arquivo OpenAPI não consegue expressar.
  • Agentes de IA. Um agente que lê OpenAPI sabe o que cada endpoint faz, mas precisa adivinhar como combiná-los. Um workflow Arazzo diz a ele.

O que mudou no Arazzo 1.1

O destaque é o suporte a AsyncAPI, para que um workflow misture chamadas HTTP com canais de mensagens. Quase todo o resto deixa os workflows HTTP mais fáceis de escrever. Está tudo no anúncio da versão:

MudançaO que faz
Fontes type: asyncapiUma descrição de origem agora pode ser um documento AsyncAPI, além de openapi e arazzo.
channelPath, action, correlationId, timeoutPassos que enviam ou recebem mensagens. action é send ou receive, correlationId liga uma resposta à sua requisição, e timeout é um limite em milissegundos.
dependsOnUm passo pode listar outros passos que precisam terminar antes de ele começar. Listar um passo não o executa.
parameters em açõesUma ação de sucesso ou falha que chama outro workflow pode passar entradas para ele, por exemplo para renovar um token e depois tentar de novo.
Selector ObjectExtrai um valor de uma resposta ou entrada com JSONPath, XPath ou JSON Pointer. Funciona em outputs, valores de parâmetros e corpos de requisição.
Expression Type ObjectFixa a versão de uma linguagem de consulta, como JSONPath conforme a RFC 9535 ou XPath 3.1. Substitui o Criterion Expression Type Object da 1.0.
in: querystringEnvia um parâmetro como a query string inteira, igual à mesma novidade do OpenAPI 3.2.
$selfUma URI que identifica o documento, para que as referências a ele não dependam de onde o arquivo está guardado.

A especificação também ganhou uma gramática completa para expressões de runtime como $steps.login.outputs.token, uma ordem definida para resolver as descrições de origem e regras exatas sobre quando uma condição conta como verdadeira. Elas não acrescentam campos, mas resolvem casos em que dois runners poderiam discordar. Suporte a gRPC, GraphQL, MCP e loops está no roadmap de versões futuras e não está na 1.1.

Um workflow que você pode rodar contra a nossa API

Este workflow cria um produto em api.testingapis.com, lê de volta, muda o preço, apaga e verifica se sumiu. Ele usa só endpoints gratuitos, então roda sem conta nem chave de API.

products.arazzo.yaml
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

Baixe junto com o arquivo OpenAPI para o qual ele aponta, coloque os dois na mesma pasta e rode. Node 22.12 ou mais recente basta, porque o npx baixa o runner:

Terminal
$ npx @redocly/cli@2.62.1 respect products.arazzo.yaml
Saída (13 de outubro de 2026)
Running 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

Algumas partes do arquivo fazem mais do que parece:

  • $response.body#/data/id é uma expressão de runtime com um JSON Pointer depois do #. Nosso endpoint de criação embrulha o produto novo em { message, data }, então o ID fica em /data/id. Todo passo seguinte lê o valor como $steps.createProduct.outputs.productId.
  • O critério em readProduct usa o novo Expression Type Object para dizer que a consulta é JSONPath da RFC 9535. Uma condição JSONPath passa quando o filtro encontra pelo menos um nó, então $[?@.category == 'electronics' && @.inStock == false] verifica dois campos de uma vez. Também invertemos os valores esperados para garantir que a verificação falha quando deve, e falhou.
  • A terceira verificação de cada passo, status code check, não está no arquivo Arazzo. O Redocly a acrescenta a partir das respostas listadas no arquivo OpenAPI, então um código de status que a API não documenta reprova o passo mesmo que seus critérios passem.

O workflow limpa quando um passo falha

Os produtos criados na nossa API são compartilhados: quem listar produtos vê o seu até ele ser apagado. Um teste que para no meio deixaria lixo para trás, então readProduct e changePrice têm uma ação onFailure que pula para deleteProduct. Para conferir, mudamos a verificação do preço para que não pudesse passar:

Saída com um passo falhando
✗ 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

O workflow ainda informa uma falha, mas o produto é apagado antes. Faça o mesmo em qualquer workflow que crie dados em um ambiente compartilhado.

Selector Objects passam no lint mas não rodam

Um Selector Object é a nova forma de tirar um valor de uma resposta. Usamos um para ler a avaliação de um produto com um JSON Pointer e mandá-la para o nosso endpoint echo, que devolve o que recebe:

selector.arazzo.yaml (passos)
- 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.rating

O redocly lint aprova este arquivo. O endpoint echo deveria receber {"rating": 4.3}. Foi isto que ele recebeu:

Corpo da requisição que o runner enviou
{
  "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"
  }
}

O runner resolveu context como o corpo inteiro da resposta e depois enviou o próprio Selector Object em vez de aplicar o seletor. Nada na saída avisa. O passo só falha porque verificamos o que o servidor recebeu; um workflow que só confere um 200 passaria enviando dados errados.

Até os runners acompanharem, use a forma da 1.0. Para um JSON Pointer ela é até mais curta:

Funciona na 1.0 e na 1.1
outputs:
  rating: $response.body#/rating/rate

Parâmetros passados para um workflow se perdem

O exemplo que o anúncio usa para parameters é uma ação de falha que chama um workflow de renovação de token e tenta o passo de novo. Nossos endpoints gratuitos não emitem tokens, então testamos o mesmo mecanismo com um produto que não existe: a ação de falha chama um workflow report que envia sua entrada reason para o endpoint echo.

params.arazzo.yaml (passo)
- 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

O runner chama o workflow report e depois tenta o passo de novo, na ordem que a especificação descreve. Mas o endpoint echo recebeu um objeto vazio:

O que o endpoint echo recebeu
"contentType": "application/json",
"contentLength": 2,
"body": "{}",
"json": {}

reason nunca chegou. Rodar report sozinho com --input reason=... funciona, então o problema está em passar parameters pela ação. Numa renovação de token, isso quer dizer que a chamada de renovação sai sem o refresh token.

O que rodou no Redocly CLI 2.62.1

Recurso do Arazzo 1.1LintExecução
arazzo: 1.1.0AceitoRoda
$selfAceitoAceito; nada para observar na execução
Expression Type ObjectAceitoRoda, e falha quando deve
dependsOnAceitoAceito; os passos rodam em ordem de qualquer jeito
Selector ObjectAceitoNão aplicado: o objeto é enviado como dado
parameters em açõesAceitoO workflow roda, as entradas chegam vazias
Passos AsyncAPINão testadoNão testado

Não encontramos nenhuma entrada no changelog do Redocly que prometa suporte ao Arazzo 1.1, então talvez sejam apenas recursos que ainda não foram construídos. As outras ferramentas listadas no repositório do Arazzo podem se comportar de outro jeito, e só testamos o Redocly.

Escrevendo workflows 1.1 agora

  • Use arazzo: 1.1.0 e os recursos que rodam: Expression Type Objects, $self e dependsOn.
  • Leia valores da resposta com expressões de runtime como $response.body#/data/id, não com Selector Objects.
  • Resolva a renovação de token fazendo login de novo em um passo, não com parameters numa ação de falha.
  • Confira o que o servidor recebeu, não só o código de status. Nem um lint aprovado nem um 200 pegaram os problemas deste post. Nosso endpoint echo pegou.
  • Fixe a versão do runner, como fizemos com @redocly/cli@2.62.1, para que uma atualização dele não mude seus resultados sem um commit.

Se o seu workflow testa um ciclo de criar, ler, atualizar e apagar, nossa lição de teste CRUD cobre quais verificações cada passo precisa. Lendo uma especificação OpenAPI (em inglês) explica os valores de operationId a que um workflow se refere, e a documentação da API de produtos lista cada campo que o workflow acima usa.