<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://usearazzo.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://usearazzo.com/" rel="alternate" type="text/html" /><updated>2026-10-10T06:41:24+00:00</updated><id>https://usearazzo.com/feed.xml</id><title type="html">UseArazzo</title><subtitle>UseArazzo is a JavaScript/TypeScript toolkit for Arazzo, the OpenAPI Initiative&apos;s specification for multi-step API workflows. Validate documents, run workflows against live APIs, and read the trace of each step.</subtitle><entry><title type="html">@usearazzo/cli Is on npm. Here Is What It Took</title><link href="https://usearazzo.com/blog/arazzo-cli-on-npm/" rel="alternate" type="text/html" title="@usearazzo/cli Is on npm. Here Is What It Took" /><published>2026-10-10T00:00:00+00:00</published><updated>2026-10-10T00:00:00+00:00</updated><id>https://usearazzo.com/blog/arazzo-cli-on-npm</id><content type="html" xml:base="https://usearazzo.com/blog/arazzo-cli-on-npm/"><![CDATA[<p>The fourth package from the UseArazzo toolkit is on npm: <a href="https://www.npmjs.com/package/@usearazzo/cli">@usearazzo/cli</a>, as an alpha. The <a href="/blog/arazzo-validator-on-npm/">validator post</a> promised that the command line would get one home, and that validation would be its first command. Well, here it is.</p>

<pre><code class="language-bash">npx @usearazzo/cli validate adopt-a-pet.arazzo.yaml
</code></pre>

<h2 id="why-a-command-line-of-its-own">Why a command line of its own?</h2>

<p>The <a href="/blog/arazzo-validator-on-npm/">validator post</a> explained why the validator’s own command line went away (<a href="https://github.com/usearazzo/arazzo-toolkit/issues/7">toolkit #7</a>). The short version: one home for the command line, not one per package.</p>

<p>So the CLI is a thin layer. It parses flags, loads a configuration file, formats the diagnostics, and sets the exit code. Everything else is the validator’s <code>validateURI</code>. That means the CLI and the library can never disagree about a document. If a rule fires in one, it fires in the other.</p>

<h2 id="how-do-i-use-it">How do I use it?</h2>

<p>Let me show you. Here is <code>adopt-a-pet.arazzo.yaml</code> from the validator post again. The two steps on lines 16 and 19 are both called <code>find-pet</code>:</p>

<pre class="numbered"><code class="language-yaml">arazzo: 1.0.1
info:
  title: Adopt a pet
  summary: Find a pet and adopt it.
  description: Find an available pet in the store and adopt it.
  version: 1.0.0
sourceDescriptions:
  - name: petstore
    url: ./petstore.openapi.json
    type: openapi
workflows:
  - workflowId: adopt-a-pet
    summary: Adopt a pet.
    description: Find an available pet and adopt it.
    steps:
      - stepId: find-pet
        description: Find an available pet.
        operationId: findPetsByStatus
      - stepId: find-pet
        description: Adopt the pet.
        operationId: updatePet
</code></pre>

<p>Last time it took a script to find them. Now it takes one command:</p>

<pre><code class="language-text">$ npx @usearazzo/cli validate adopt-a-pet.arazzo.yaml
adopt-a-pet.arazzo.yaml
  16:9-18:38  error  9040403  Every step must have a unique 'stepId' within a workflow.
  19:9-22:1   error  9040403  Every step must have a unique 'stepId' within a workflow.

✖ 2 problems (2 errors)
</code></pre>

<p>Quite simple. Notice that lines count from 1 here, not from 0. That’s the script’s <code>+ 1</code>, done for you, so you can jump straight to the line in your editor. The number in the third column is the rule’s code, and the <a href="/docs/validator/#rules">Validator API reference</a> lists every one.</p>

<p>Prefer it installed? <code>npm install --global @usearazzo/cli</code> gives you a <code>usearazzo</code> binary. The argument can be a path, a <code>file:</code> URI, or an HTTP(S) URL.</p>

<p>From here, the place to go is the <a href="/docs/cli/">CLI reference</a>. It covers every option, both output formats, the exit codes, and the configuration file.</p>

<h2 id="what-does-a-pipeline-actually-read">What does a pipeline actually read?</h2>

<p>Not the output. It reads the exit code. People wire a command into CI and stop looking at it, so the exit code has to mean exactly one thing, every time.</p>

<p>By default, only errors fail a run. A warning is advice, not a violation, so a document with warnings exits with <code>0</code>. Take <code>onboarding.arazzo.yaml</code>, whose only problem is a step without a description. It passes. Want stricter? Lower the threshold:</p>

<pre><code class="language-text">$ npx @usearazzo/cli validate onboarding.arazzo.yaml --fail-severity warning
onboarding.arazzo.yaml
  16:9-18:1  warning  9050201  Step 'description' should be present and non-empty string.

⚠ 1 problem (1 warning)
$ echo $?
1
</code></pre>

<p>Now, what if the report is long? A hundred problems clutter a CI log, so <code>--max-problems</code> caps the list. But we can see an obvious problem here. Picture a hundred warnings and one error at the very end. Show the first ten, and the error isn’t among them. Should the run pass? Of course not. So the exit code is computed from every problem, before the list is cut, and the output says how many were left out:</p>

<pre><code class="language-text">$ npx @usearazzo/cli validate adopt-a-pet.arazzo.yaml --max-problems 1
adopt-a-pet.arazzo.yaml
  16:9-18:38  error  9040403  Every step must have a unique 'stepId' within a workflow.

✖ 1 problem (1 error)
(showing 1 of 2 problems)
</code></pre>

<p>There’s one more trap, and it’s a quiet one. The obvious way to end a Node.js CLI is <code>process.exit()</code>. Well, it can cut your output short. When stdout is a pipe, writes are asynchronous, and exiting early drops whatever is still in the buffer, at around 64 KB. A big JSON report piped into another tool would arrive broken, with no error anywhere. So the CLI sets the exit code and lets the process end on its own. The test suite pipes more than 64 KB of JSON through it, to keep it that way.</p>

<h2 id="can-it-overwrite-my-files">Can it overwrite my files?</h2>

<p>No. A validator reads your files. It should never write over them.</p>

<p><code>-o</code> writes the report to a file. Point it at the input document by mistake, and you’d replace your workflow with a list of its problems. So the CLI refuses:</p>

<pre><code class="language-text">$ npx @usearazzo/cli validate adopt-a-pet.arazzo.yaml -o adopt-a-pet.arazzo.yaml
Error: --output path must differ from the input file
</code></pre>

<p>It’s important to realize that comparing path strings isn’t enough for that. A symlink, a hard link, or a case-insensitive file system can give the same file a different name. So the CLI also compares what the paths point at on disk.</p>

<h3 id="a-word-on-the-configuration-file">A word on the configuration file</h3>

<p>The configuration file follows the same thinking. It holds your validation settings, and the CLI finds it in the working directory: <code>.usearazzo.yaml</code>, <code>usearazzo.json</code>, and a few spellings in between. It’s YAML or JSON only, and that’s deliberate.</p>

<h2 id="what-does-alpha-mean-here">What does alpha mean here?</h2>

<p>Alpha means options and output can still move before 1.0. Concretely:</p>

<ul>
  <li>One command, <code>validate</code>. It accepts the same versions as the rest of the toolkit: Arazzo 1.0.0, 1.0.1, and 1.1.0, in JSON and YAML.</li>
  <li>Two output formats. <code>stylish</code> is for people. <code>--json</code> prints Language Server Protocol diagnostics, exactly what the library returns, for other tools.</li>
  <li>Exit code <code>1</code> when a problem at or above <code>--fail-severity</code> is found, or when the run can’t finish. <code>0</code> otherwise.</li>
  <li>Settings come from the validator’s defaults, then the configuration file, then the flags. The last one wins.</li>
  <li><code>--help</code> and <code>--version</code> answer in about a tenth of a second. The validator is loaded only when a command needs it.</li>
</ul>

<p><strong>The feedback I want most:</strong></p>

<ul>
  <li>Which flag did you reach for that isn’t there?</li>
  <li>What does your CI need from the output that it doesn’t get?</li>
</ul>

<p><a href="https://github.com/orgs/usearazzo/discussions">Discussions</a> is the place, and the <a href="https://github.com/usearazzo/arazzo-toolkit">monorepo</a> is where the issues go.</p>

<h2 id="what-are-the-future-plans">What are the future plans?</h2>

<p>Two things matter most to me for the CLI itself.</p>

<p>The first is <strong>custom rules</strong>. The built-in rules check what the Arazzo specification says. But every team has conventions of its own: how a <code>stepId</code> is named, which fields a workflow must carry, which source descriptions are allowed. Those belong in the same run, with the same output and the same exit code. The catch is that a rule is code, and a YAML configuration file can only carry data. Rules would arrive through JavaScript configuration files. Loading one means running code from whatever directory you validate, so that’s deferred on purpose until it has a careful design (<a href="https://github.com/usearazzo/arazzo-toolkit/issues/219">toolkit #219</a>).</p>

<p>The second is <strong>more output formats</strong>. Today there are two: <code>stylish</code> for people, and <code>json</code> for other tools. The validator’s old command line also had a <code>codeframe</code> format, which prints the offending lines of the document under each problem, and a <code>github-actions</code> format, which turns each problem into an annotation on the pull request diff (<a href="https://github.com/usearazzo/arazzo-toolkit/issues/7">toolkit #7</a>). They didn’t make the first release of the CLI, and I’d like them back.</p>

<p>Beyond validation, the CLI will grow commands for the rest of the toolkit (<a href="https://github.com/usearazzo/arazzo-toolkit/issues/84">toolkit #84</a>). <code>run-workflow</code> and <code>run-operation</code> arrive when the <a href="/runner/">Runner</a> is published, and the <a href="/cli/#commands">CLI page</a> describes the rest.</p>

<p>Which of these would you use first? Nothing here is built yet, so this is the cheapest moment to change it.</p>

<h2 id="closing-words">Closing words</h2>

<p>Validating an Arazzo document in CI is now one line in your workflow file. No script, no glue. Sounds pretty easy, right? Give it a try, and let me know in <a href="https://github.com/orgs/usearazzo/discussions">Discussions</a> which flag you missed.</p>]]></content><author><name>Vladimír Gorej</name></author><summary type="html"><![CDATA[The UseArazzo CLI is published, as an alpha, with one command: validate. Why a command line is a contract, and what it took to keep it.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://usearazzo.com/assets/images/blog/arazzo-cli-on-npm.png" /><media:content medium="image" url="https://usearazzo.com/assets/images/blog/arazzo-cli-on-npm.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">@usearazzo/validator Is on npm. Here Is What It Took</title><link href="https://usearazzo.com/blog/arazzo-validator-on-npm/" rel="alternate" type="text/html" title="@usearazzo/validator Is on npm. Here Is What It Took" /><published>2026-10-06T00:00:00+00:00</published><updated>2026-10-06T00:00:00+00:00</updated><id>https://usearazzo.com/blog/arazzo-validator-on-npm</id><content type="html" xml:base="https://usearazzo.com/blog/arazzo-validator-on-npm/"><![CDATA[<p>The third package from the UseArazzo toolkit is on npm: <a href="https://www.npmjs.com/package/@usearazzo/validator">@usearazzo/validator</a>, as an alpha. The <a href="/blog/arazzo-parser-on-npm/">parser</a> reads an Arazzo document. The <a href="/blog/arazzo-resolver-on-npm/">resolver</a> follows its references. The validator tells you what is wrong with it, before a workflow runs and fails halfway through.</p>

<pre><code class="language-bash">npm install @usearazzo/validator
</code></pre>

<p>This post is about one idea. When the validator reports nothing, your document has to be fine. Most of the work before publishing went into making that true.</p>

<h2 id="how-do-i-use-it">How do I use it?</h2>

<p>Let me show you the simplest use. Point it at a file, keep the errors, and fail when there are any:</p>

<pre><code class="language-js">import { validateURI, DiagnosticSeverity } from '@usearazzo/validator';

const diagnostics = await validateURI('./adopt-a-pet.arazzo.yaml');
const errors = diagnostics.filter((d) =&gt; d.severity === DiagnosticSeverity.Error);

for (const { range, message } of errors) {
  console.log(`${range.start.line + 1}:${range.start.character + 1}  ${message}`);
}
console.log(`${errors.length} errors`);
process.exitCode = errors.length &gt; 0 ? 1 : 0;
</code></pre>

<p>Here is <code>adopt-a-pet.arazzo.yaml</code>. The two steps on lines 16 and 19 are both called <code>find-pet</code>:</p>

<pre class="numbered"><code class="language-yaml">arazzo: 1.0.1
info:
  title: Adopt a pet
  summary: Find a pet and adopt it.
  description: Find an available pet in the store and adopt it.
  version: 1.0.0
sourceDescriptions:
  - name: petstore
    url: ./petstore.openapi.json
    type: openapi
workflows:
  - workflowId: adopt-a-pet
    summary: Adopt a pet.
    description: Find an available pet and adopt it.
    steps:
      - stepId: find-pet
        description: Find an available pet.
        operationId: findPetsByStatus
      - stepId: find-pet
        description: Adopt the pet.
        operationId: updatePet
</code></pre>

<p>Run the script, and it finds both:</p>

<pre><code class="language-text">16:9  Every step must have a unique 'stepId' within a workflow.
19:9  Every step must have a unique 'stepId' within a workflow.
2 errors
</code></pre>

<p>Why the <code>+ 1</code>? Diagnostics count lines and characters from 0, the way editors do.</p>

<p>Already holding the text, say in an editor or a test? Then skip the file and use <code>validate</code>:</p>

<pre><code class="language-js">import { validate, createTextDocument } from '@usearazzo/validator';

const textDocument = createTextDocument('file:///home/you/adopt-a-pet.arazzo.yaml', yamlText);
const diagnostics = await validate(textDocument);
</code></pre>

<p>From there, the place to go is the <a href="/docs/validator/">Arazzo Validator API reference</a>. It covers every function, option, and error, and all 251 rules, with each message quoted exactly as the validator reports it.</p>

<h2 id="what-do-you-get-back">What do you get back?</h2>

<p>Each line the script printed is one diagnostic. Here is the first one in full:</p>

<pre><code class="language-json">{
  "range": { "start": { "line": 15, "character": 8 }, "end": { "line": 17, "character": 37 } },
  "severity": 1,
  "code": 9040403,
  "source": "apilint",
  "message": "Every step must have a unique 'stepId' within a workflow."
}
</code></pre>

<p>As you can see, this is a <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic">Language Server Protocol diagnostic</a>. VS Code and every other LSP client already know how to render it. So the same output works in an editor, in a script, and in CI. Nothing needs translating.</p>

<p>Notice that nothing was thrown. A problem in the document is always a diagnostic. The validator throws only when it cannot read the document at all. Then you get a typed <code>ValidateError</code>, with the original error on <code>cause</code> (<a href="https://github.com/usearazzo/arazzo-toolkit/pull/201">toolkit #201</a>). So there are exactly two outcomes: a list, or the news that the document never arrived.</p>

<p>Why does it matter? Because of what people do with the list. They count the errors, and zero means “valid”. A validator has to earn that trust, and it can lose it in two ways:</p>

<ul>
  <li>by saying nothing when something is wrong</li>
  <li>by saying something when nothing is</li>
</ul>

<p>I ran into both while getting this package ready.</p>

<h2 id="when-silence-was-not-a-pass">When silence was not a pass</h2>

<p>The most dangerous bug in a validator is the one that produces no output. Nobody files an issue about a document that passed.</p>

<ul>
  <li><strong>Arazzo 1.1.0 documents were never checked against JSON Schema.</strong> With JSON Schema validation turned on, an Arazzo 1.0.x document was checked against the 1.0 schema. A 1.1.0 document was checked against nothing. The 1.1 schema provider existed upstream. We simply never registered it in our defaults. So a 1.1.0 document looked like it had passed (<a href="https://github.com/usearazzo/arazzo-toolkit/pull/202">toolkit #202</a>).</li>
  <li><strong>The README promised a check that does not exist.</strong> It said relative external <code>$ref</code>s in a JSON Schema resolve against a <code>baseURI</code> you pass in. I pointed a <code>$ref</code> at a missing file and got zero diagnostics, with <code>baseURI</code> and without. The <code>referenceValidation</code> check covers local <code>#/...</code> pointers only. The README now says so, and so does the <a href="/docs/validator/">API reference</a> (<a href="https://github.com/usearazzo/arazzo-toolkit/pull/204">toolkit #204</a>).</li>
</ul>

<p>How did I find them? The same way as before: by writing the API reference for this site and running every sample in it. With the parser, the docs turned up five bugs. With the resolver, two of the ten. This time they caught the one that mattered most. Well, at least it was caught in time. The 1.1.0 fix landed before the first publish, so no published version has the bug.</p>

<h2 id="when-noise-was-not-an-error">When noise was not an error</h2>

<p>The opposite failure is just as bad. A validator that reports errors on a valid document teaches people to ignore it.</p>

<p>Here is the one I hit. Every Reusable Object reported three errors. Reference a parameter, a success action, or a failure action from <code>components</code>, and the validator demanded fields a Reusable Object must not have. It also rejected <code>reference</code> as a field that is not allowed.</p>

<p>Why? The rules for a Parameter Object also ran on a Reusable Object that points at one. The OpenAPI rules guard against exactly this case. The Arazzo rules were written without the guard. So a document using Reusable Objects in all six places it can reported 18 errors. Now it reports none (<a href="https://github.com/usearazzo/arazzo-toolkit/issues/17">toolkit #17</a>).</p>

<h2 id="when-it-read-too-much">When it read too much</h2>

<p>There is a third way to lose trust, and it has nothing to do with diagnostics. An Arazzo document decides what its source descriptions point at, and the validator fetches them.</p>

<p>The default allow list for local files was <code>['*']</code>. Looks harmless, right? In practice it let a document name any file on disk, <code>/etc/passwd</code> included, and the validator would read it. And because a glob like <code>'*'</code> never matches a dotfile, a perfectly ordinary <code>./.petstore.json</code> was skipped. So the default was too open and too closed at the same time. Now it is the parser’s: local <code>.json</code>, <code>.yaml</code>, and <code>.yml</code> files, dotfiles included, and nothing else (<a href="https://github.com/usearazzo/arazzo-toolkit/pull/201">toolkit #201</a>).</p>

<p>Validating a document you did not write? The API reference has a section on <a href="/docs/validator/#untrusted">untrusted documents</a>. It shows how to turn resolution off, or how to keep it inside one directory.</p>

<h2 id="what-does-alpha-mean-here">What does alpha mean here?</h2>

<p>Alpha means the API can still move before 1.0. Concretely:</p>

<ul>
  <li>The surface is two functions. <code>validateURI</code> takes a path or a URL. <code>validate</code> takes a document already in memory, as a <code>TextDocument</code>, which is what an editor holds.</li>
  <li>251 rules run by default, grouped by the object they check. Each has a stable numeric code to match on.</li>
  <li>Warnings and hints are advice, not violations. To decide whether a document is valid, count errors only.</li>
  <li>JSON Schema validation is opt-in. The rules already report most of what it finds, so turning it on mostly doubles the output.</li>
  <li>The same versions as the parser and the resolver: Arazzo 1.0.0, 1.0.1, and 1.1.0, in JSON and YAML.</li>
  <li>The checks come from the SpecLynx ApiDOM Language Service, which I also maintain. Documents are read with the parser.</li>
</ul>

<p><strong>The feedback I want most:</strong></p>

<ul>
  <li>Which rule fired on a document you know is fine?</li>
  <li>Which mistake got through that should not have?</li>
</ul>

<p><a href="https://github.com/orgs/usearazzo/discussions">Discussions</a> is the place, and the <a href="https://github.com/usearazzo/arazzo-toolkit">monorepo</a> is where the issues go.</p>

<h2 id="what-are-the-future-plans">What are the future plans?</h2>

<p>Two things.</p>

<p>The first is more rules. My goal is to uncover as much as possible without running a workflow against a real API. Today the rules check a document’s own shape. Next they check what the document means:</p>

<ul>
  <li><strong>Runtime expressions,</strong> fully. Not only that each one parses, but that it sits in a position where it makes sense. The <a href="/docs/tutorials/parse-arazzo-runtime-expressions/#where-parsing-ends">runtime expressions tutorial</a> ends exactly here: <code>$response.body#/id</code> as a parameter value parses fine and is still wrong.</li>
  <li><strong>Criterion objects,</strong> with their conditions, JSONPath queries, and JSON Pointers.</li>
  <li><strong>Source descriptions.</strong> The validator already fetches and parses them. The next rules read them. Does a step’s <code>operationId</code> really exist in the OpenAPI document it names? Does a <code>workflowId</code> exist in the Arazzo document it names?</li>
</ul>

<p>That list lives in <a href="https://github.com/usearazzo/arazzo-toolkit/issues/197">toolkit #197</a>. If you have a rule idea, add it there.</p>

<p>The second is the CLI. The validator used to ship a command-line interface of its own. We took it out on purpose (<a href="https://github.com/usearazzo/arazzo-toolkit/issues/7">toolkit #7</a>). Two entry points for one job would only confuse people. And every package would carry its own argument parsing, output formats, and exit codes. So the command line gets one home, the <a href="/cli/">UseArazzo CLI</a>, and validation will be its first command. It is <strong>coming soon</strong>. Let me show you how it will look:</p>

<p class="mb-3"><span class="badge badge-secondary">Coming soon</span></p>
<div class="terminal mb-4">
        <div class="terminal-titlebar">
          <span class="terminal-dot terminal-dot-red"></span>
          <span class="terminal-dot terminal-dot-yellow"></span>
          <span class="terminal-dot terminal-dot-green"></span>
          <span class="terminal-title">usearazzo validate (preview)</span>
        </div>
<!-- Two runs. JS types each command and reveals its output in a loop; without JS (or with
     reduced motion) the runs render stacked. Diagnostics are verbatim validateURI output for
     the two sample documents, with ranges shown 1-based. -->
<pre class="terminal-body terminal-panel" role="region" tabindex="0" aria-label="Preview of the planned validate command on two Arazzo documents" data-terminal-typewriter><span class="terminal-run"><span class="terminal-prompt"><span class="t-dollar">$</span> npx @usearazzo/cli validate <span class="t-arg">adopt-a-pet.arazzo.yaml</span></span>
<span class="t-file">adopt-a-pet.arazzo.yaml</span>
  <span class="t-loc">16:9-18:38</span>  <span class="t-error">error</span>  <span class="t-code">9040403</span>  <span class="t-msg">Every step must have a unique 'stepId' within a workflow.</span>
  <span class="t-loc">19:9-22:1 </span>  <span class="t-error">error</span>  <span class="t-code">9040403</span>  <span class="t-msg">Every step must have a unique 'stepId' within a workflow.</span>

<span class="t-fail">&#10006; 2 problems (2 errors)</span></span><span class="terminal-run"><span class="terminal-prompt"><span class="t-dollar">$</span> npx @usearazzo/cli validate <span class="t-arg">onboarding.arazzo.yaml</span></span>
<span class="t-file">onboarding.arazzo.yaml</span>
  <span class="t-loc">16:9-17:32</span>  <span class="t-warn">warning</span>  <span class="t-code">9050201</span>  <span class="t-msg">Step 'description' should be present and non-empty string.</span>

<span class="t-warnsum">&#9888; 1 problem (1 warning)</span></span></pre>
</div>

<p>The command and its formatting are a preview. The diagnostics are not. Each one is what the validator reports for that document today, with lines counted from 1. Compare the two runs. The first is the document from above, with its two errors. The second has only a warning, which is advice, not a violation.</p>

<p>So there it is. Until the CLI arrives, the library is there to be used. Start with the <a href="/docs/validator/">Arazzo Validator API reference</a>, point it at a workflow you have written, and tell me what it got wrong.</p>]]></content><author><name>Vladimír Gorej</name></author><summary type="html"><![CDATA[The third UseArazzo package is published, as an alpha. Why an empty list of diagnostics has to mean something, and what it took to get there.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://usearazzo.com/assets/images/blog/arazzo-validator-on-npm.png" /><media:content medium="image" url="https://usearazzo.com/assets/images/blog/arazzo-validator-on-npm.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">@usearazzo/resolver Is on npm. Here Is What It Took</title><link href="https://usearazzo.com/blog/arazzo-resolver-on-npm/" rel="alternate" type="text/html" title="@usearazzo/resolver Is on npm. Here Is What It Took" /><published>2026-09-21T00:00:00+00:00</published><updated>2026-09-21T00:00:00+00:00</updated><id>https://usearazzo.com/blog/arazzo-resolver-on-npm</id><content type="html" xml:base="https://usearazzo.com/blog/arazzo-resolver-on-npm/"><![CDATA[<p>The second package from the UseArazzo toolkit is on npm: <a href="https://www.npmjs.com/package/@usearazzo/resolver">@usearazzo/resolver</a>, as an alpha. The <a href="/blog/arazzo-parser-on-npm/">parser</a> reads an Arazzo document and leaves every reference in it exactly as written. The resolver is the package that follows them: <code>$ref</code>s, JSON Schema references, and the Reusable Objects that point into <code>components</code>, in Arazzo documents and in the OpenAPI documents they name.</p>

<pre><code class="language-bash">npm install @usearazzo/resolver
</code></pre>

<p>The place to start is the <a href="/docs/resolver/">Arazzo Resolver API reference</a>. It covers every function, option, and error, and every output on that page came from a real run.</p>

<p>The rest of this post is about why “resolving” turned out to be three different jobs, and about the ten fixes it took in the layer underneath, in dereferencing and bundling both.</p>

<h2 id="three-jobs-behind-one-word">Three jobs behind one word</h2>

<p>Ask three tool builders to “resolve the references” and you get three different programs. So the resolver has three operations, and the first decision you make is which one you need.</p>

<ul>
  <li><strong>Dereference</strong> replaces every reference with the content it points at. You get a document with no references left. This is what you want before executing a workflow, which is why the <a href="/runner/">Runner</a> depends on this package.</li>
  <li><strong>Bundle</strong> copies every external target into the entry document. You get one self-contained file that still reads like the original, references and all.</li>
  <li><strong>Resolve</strong> leaves the references alone. It fetches and parses every document they reach, and hands you the set. This is what you want when the reference graph itself is the subject, for example to list every file a workflow depends on.</li>
</ul>

<p>Why is this a separate package, and not an option on the parser? Because following a reference means file system and network access, caching, and cycle rules of its own. A parser that did all of that on every call would be the wrong tool for an editor that just wants a tree. The <a href="/docs/guides/arazzo-document-parsing/#not-parsing">Parsing Arazzo Documents</a> guide draws that line: a parser reads, a resolver dereferences.</p>

<h2 id="identity-is-not-location">Identity is not location</h2>

<p>The thing that took longest to get right is one idea, and it sits under dereferencing and bundling alike.</p>

<p>I started out thinking a reference points at a place: a file, and a path inside it. For the Reference Objects of OpenAPI 2.0 and 3.0 that is true, and both operations are simple. Dereferencing reads the place. Bundling copies it into <code>components</code> and points the reference at the copy.</p>

<p>JSON Schema 2020-12 changes it, and both OpenAPI 3.1 and Arazzo use it. A schema can declare its own identity with <code>$id</code>. Every relative <code>$ref</code> inside it resolves against that identity, not against the file it happens to sit in. So a resolver needs an index of identities, and that index has to stay right while the document changes under it.</p>

<p>Dereferencing reads that index. Bundling rewrites the document the index describes, so it is where most of the trouble showed up. Bundling is not copying. It is <strong>relocating</strong>, and relocating a schema changes what every relative reference around it means. One sentence sums it up: a bundle is only a bundle if you can move it. If the output still needs the directory it was made in, or the network, it is not self-contained, whatever it looks like.</p>

<h2 id="what-it-took-upstream">What it took upstream</h2>

<p>The resolver stands on <a href="https://github.com/speclynx/apidom">SpecLynx ApiDOM</a>, which I also maintain, and that is where almost all of the work went. Ten issues, closed in two patch releases over four days.</p>

<p>It started with a review comment. The pull request that <a href="https://github.com/usearazzo/arazzo-toolkit/pull/157">prepared the package for publishing</a> came with a bundle fixture, and a comment on its expected output pointed out that the output was wrong. The fixture had enshrined the bug. That became <a href="https://github.com/usearazzo/arazzo-toolkit/issues/158">toolkit #158</a>, and pulling on it unravelled the rest.</p>

<p>In bundling:</p>

<ul>
  <li><strong>A schema with its own <code>$id</code> was embedded, and then nothing pointed at it.</strong> The file was fetched as <code>./inputs.json</code> but called itself <code>https://example.com/schemas/inputs.json</code>. The bundler embedded it under its own name and left the <code>$ref</code> as written, so the reference dangled. Now the <code>$ref</code> is rebased onto the embedded <code>$id</code> (<a href="https://github.com/speclynx/apidom/issues/530">#530</a>).</li>
  <li><strong>A schema without an <code>$id</code> got an absolute path from my machine as one.</strong> The bundler assigned the retrieval URI, which for a file is an absolute path. The bundle leaked a machine-specific path and only worked in the directory it was made in. It now gets an <code>$id</code> relative to the entry document (<a href="https://github.com/speclynx/apidom/issues/531">#531</a>).</li>
  <li><strong>Everything broke as soon as a schema lived in a subdirectory.</strong> Components inserted during the walk were visited again, with the entry document’s base URI. Every existing fixture kept all its files in one directory, which is exactly what hid it (<a href="https://github.com/speclynx/apidom/issues/535">#535</a>).</li>
  <li><strong>Hoisting a Response or a Parameter out of another OpenAPI 3.1 document lost its schemas.</strong> Relative schema <code>$ref</code>s inside it were carried along with the wrong base (<a href="https://github.com/speclynx/apidom/issues/537">#537</a>). A schema it named elsewhere in the same document was never brought along (<a href="https://github.com/speclynx/apidom/issues/546">#546</a>).</li>
</ul>

<p>In the <code>$id</code> lookup that dereferencing and bundling share:</p>

<ul>
  <li><strong>The <code>$id</code> index was a snapshot.</strong> Lookups went through metadata recorded once, at parse time, and the list was cached. Any <code>$id</code> assigned or changed afterwards was invisible. Worse, a schema with no identifiers matched any URI. Lookups now walk the real elements (<a href="https://github.com/speclynx/apidom/issues/536">#536</a>, <a href="https://github.com/speclynx/apidom/issues/540">#540</a>).</li>
  <li><strong>A schema inside a referenced fragment could not find itself.</strong> A Response in another file held a schema with an absolute <code>$id</code> and a <code>$ref</code> to its own <code>$defs</code>. The <code>$id</code> was never indexed, so both operations fell back to fetching that <code>$id</code> over HTTP (<a href="https://github.com/speclynx/apidom/issues/541">#541</a>).</li>
</ul>

<p>All of that shipped in <a href="https://github.com/speclynx/apidom/releases/tag/v5.2.5">one ApiDOM release</a>, and the resolver was published the next day.</p>

<p>Then I wrote the reference page, ran every sample, and two more things broke. Both are in dereferencing.</p>

<ul>
  <li><strong><code>continueOnError</code> did not cover Reusable Objects.</strong> A linter wants to process everything that works and report the rest. One bad <code>reference: $components.parameters.missing</code> still threw (<a href="https://github.com/speclynx/apidom/issues/550">#550</a>).</li>
  <li><strong>Source descriptions came back as the wrong document.</strong> Through the <code>Element</code> functions and both resolve functions, the <code>petstore</code> entry held the Arazzo document again, not the OpenAPI document it names (<a href="https://github.com/speclynx/apidom/issues/551">#551</a>).</li>
</ul>

<p>Both were fixed in <a href="https://github.com/speclynx/apidom/releases/tag/v5.2.6">the next release</a>, a day later, and the published resolver carries it. Both reproductions now pass against the package from npm.</p>

<p>Two are still open, both in dereferencing, and neither blocks normal use. A schema <code>$ref</code> cannot be served from a pre-computed set when every resolver is switched off (<a href="https://github.com/speclynx/apidom/issues/554">#554</a>), and the <code>location</code> of an error inside a referenced document repeats a fragment (<a href="https://github.com/speclynx/apidom/issues/555">#555</a>).</p>

<p>The lesson from the parser was that the samples in the docs are the best test suite a package has. That held again: two of the ten came from writing the reference. The new lesson is about fixtures. A fixture whose files all sit in one directory tests nothing about location, and location is the whole problem. And a saved expected output proves only that the output has not changed, never that it was right. The <code>bundled.json</code> that started all this was the bundler’s own broken output, saved and then asserted against. The test was green the whole time.</p>

<h2 id="what-alpha-means-here">What alpha means here</h2>

<p>Alpha means the API can still move before 1.0. Concretely:</p>

<ul>
  <li>The surface is ten functions: dereference, bundle, and resolve, each for Arazzo and for OpenAPI, with <code>Element</code> variants for dereference and resolve that take a tree you already parsed. Only whole documents bundle.</li>
  <li>A dereferenced result is not a tree. Each reference site gets its own top-level element, but everything beneath it is shared, so treat it as a graph. A change made under one reference site shows up under the others.</li>
  <li>Option names follow ApiDOM’s: <code>resolve</code>, <code>parse</code>, <code>dereference</code>, <code>bundle</code>. The Runner is the only consumer so far, so they may still move as other tools start using the package.</li>
  <li>The same versions as the parser: Arazzo 1.0.0, 1.0.1, and 1.1.0, with OpenAPI 2.0, 3.0.x, and 3.1.x. OpenAPI 3.2 and AsyncAPI are not supported yet.</li>
</ul>

<p>The feedback I want most is the same as before, from people building their own Arazzo tooling: what did you have to work around, and what did you need that is not there? <a href="https://github.com/orgs/usearazzo/discussions">Discussions</a> is the place, and the <a href="https://github.com/usearazzo/arazzo-toolkit">monorepo</a> is where the issues go.</p>

<h2 id="what-comes-next">What comes next</h2>

<p>The Validator and the Runner are both in development and both unpublished. They now stand on two published layers, not one: a parser that reads, and a resolver that follows.</p>

<p>Until then, the resolver is there to be used. Start with the <a href="/docs/resolver/">Arazzo Resolver API reference</a>, bundle something that lives in more than one directory, move the result somewhere else, and tell me what breaks.</p>]]></content><author><name>Vladimír Gorej</name></author><summary type="html"><![CDATA[The second UseArazzo package is published, as an alpha. Why resolving references is three jobs, why a schema's identity is not its location, and the ten fixes it took in the layer underneath.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://usearazzo.com/assets/images/blog/arazzo-resolver-on-npm.png" /><media:content medium="image" url="https://usearazzo.com/assets/images/blog/arazzo-resolver-on-npm.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">@usearazzo/parser Is on npm. Here Is What It Took</title><link href="https://usearazzo.com/blog/arazzo-parser-on-npm/" rel="alternate" type="text/html" title="@usearazzo/parser Is on npm. Here Is What It Took" /><published>2026-09-08T00:00:00+00:00</published><updated>2026-09-08T00:00:00+00:00</updated><id>https://usearazzo.com/blog/arazzo-parser-on-npm</id><content type="html" xml:base="https://usearazzo.com/blog/arazzo-parser-on-npm/"><![CDATA[<p>The first package from the UseArazzo toolkit is on npm: <a href="https://www.npmjs.com/package/@usearazzo/parser">@usearazzo/parser</a>, as an alpha. It reads Arazzo documents, the OpenAPI documents they point at, and the two small languages that live inside Arazzo strings, runtime expressions and criterion conditions. It is the reading layer everything else in the toolkit stands on.</p>

<pre><code class="language-bash">npm install @usearazzo/parser
</code></pre>

<p>If you want to see it do something before reading further, the <a href="/docs/tutorials/list-arazzo-workflow-dependencies/">first tutorial</a> builds a sixty-line script that lists every document a workflow depends on. It runs against the published package as-is.</p>

<p>The rest of this post is about why this package went first, and what I learned getting it out the door.</p>

<h2 id="why-the-parser-and-not-the-validator-or-the-runner">Why the parser, and not the Validator or the Runner</h2>

<p>A few weeks ago I wrote that <a href="/blog/api-workflows-are-still-improvised/">API workflows are still improvised</a> and that UseArazzo is building the tooling to change that. The tools people actually want are the <a href="/validator/">Validator</a> and the <a href="/runner/">Runner</a>. Nobody wakes up wanting a parser.</p>

<p>But both of those tools need the same three things from the document before they can do their job. They need to know what every node <em>is</em>: that a string in a <code>value</code> field is a runtime expression and not just text, that <code>steps</code> is a list of Step Objects and not a bag of maps. They need to know <em>where</em> every node is, line and column, because a diagnostic without a position is a riddle and a run failure without one is a support ticket. And they need every document the workflow reaches, since a step can call an operation in one OpenAPI description and a workflow in another Arazzo document, each with source descriptions of its own.</p>

<p>The Runner already had its own expression parsing, and the Validator needs the same trees. Shipping either of them first would have meant shipping the same parser twice, privately, and fixing every bug in two places. So the parser came out alone, with its API documented. The Runner was <a href="https://github.com/usearazzo/arazzo-toolkit/pull/134">moved onto it</a> before the alpha was cut, and the Validator already depends on it.</p>

<p>There is a second reason. The <a href="/ecosystem/">Arazzo Ecosystem</a> page lists more than twenty tools, and most of them started the same way: someone loaded a YAML file and got to work. I would rather the next editor plugin, linter, or agent integration not have to start from a YAML loader. A parser that is a dependency, not a product, is a better contribution to that ecosystem than a product would be.</p>

<h2 id="what-the-document-is">What the document is</h2>

<p>The thing that took longest to get right is not any one feature. It is a shift in what I thought I was parsing.</p>

<p>I started out thinking of an Arazzo document as a file. It is not. It is several languages in one file: YAML or JSON on the outside, runtime expressions inside the strings, a condition grammar inside the criteria, JSON Pointer inside the expressions, JSON Schema for the inputs. And it is one file in a network: its source descriptions point at OpenAPI documents and at other Arazzo documents, and those point onwards. What a workflow means is spread across that network, so a parser that stops at the file has not finished.</p>

<p>The <a href="/docs/guides/arazzo-document-parsing/">Parsing Arazzo Documents</a> guide is the long version of that argument. What matters for this post is that once I took the network seriously, the bugs showed up on their own.</p>

<h2 id="what-writing-the-docs-turned-up">What writing the docs turned up</h2>

<p>Every bug fixed in the fortnight before the release was found by writing documentation, not by writing code. I sat down to write the guide, ran every sample in it against the package, and four things broke.</p>

<ul>
  <li><strong>Shared documents looked like cycles.</strong> Two workflow documents naming the same OpenAPI description is the normal case, not a loop. The parser treated the second reference as a cycle, cut it, and dropped the parsed document on the floor. Fixed in <a href="https://github.com/usearazzo/arazzo-toolkit/pull/142">#142</a>: a document reached twice is parsed once, and every later reference points at the first parse.</li>
  <li><strong>The in-memory resolver served the wrong document.</strong> Inline content is parsed under a synthetic <code>memory://</code> URI, and the resolver serving it answered for <em>any</em> <code>memory://</code> URI, so a relative source description that could not be found came back as the parent document itself. Fixed in <a href="https://github.com/usearazzo/arazzo-toolkit/pull/136">#136</a>.</li>
  <li><strong>Inline input had nowhere to resolve from.</strong> If you hand the parser a string or an object, a relative source description URL has no base to resolve against. <code>resolve.baseURI</code> now supplies one, in <a href="https://github.com/usearazzo/arazzo-toolkit/pull/138">#138</a>.</li>
  <li><strong><code>ParseError</code> was not exported.</strong> You could catch it, but not by type. <a href="https://github.com/usearazzo/arazzo-toolkit/pull/141">#141</a>.</li>
</ul>

<p>Then I wrote the tutorial, ran its script, and found a fifth: pass a <em>relative</em> file path and the parser read the file fine, but resolved the source descriptions against the relative string as if it were a URL path. Reported as <a href="https://github.com/usearazzo/arazzo-toolkit/issues/147">#147</a> and fixed in <a href="https://github.com/usearazzo/arazzo-toolkit/pull/148">#148</a> the same day: a relative path now resolves against the working directory, in the parser, the resolver, and the Runner’s document registry alike.</p>

<p>I am not embarrassed by the list. Four of the five are about the network, and they are exactly the bugs you would expect from a parser written by someone who thought of a document as a file. The lesson I am taking is procedural: the samples in the docs are the best test suite the package has, because they are the tests a user would write.</p>

<h2 id="what-alpha-means-here">What alpha means here</h2>

<p>Alpha means the API can still move before 1.0. Concretely:</p>

<ul>
  <li>The four functions, <code>parseArazzo</code>, <code>parseOpenAPI</code>, <code>parseRuntimeExpression</code>, and <code>parseCriterionCondition</code>, and their result shapes are what I intend to keep. The <a href="/docs/parser/">API reference</a> documents every option and every error they produce, and was checked against the type declarations and the source, not the README.</li>
  <li>Option names under <code>parse.parserOpts</code> may change as the Validator starts consuming the package and wants things the Runner did not.</li>
  <li>The documents come back as <a href="https://github.com/speclynx/apidom">SpecLynx ApiDOM</a>, which I also maintain. That is a dependency you are taking on, and it is not going anywhere.</li>
  <li>Arazzo 1.0.0, 1.0.1, and 1.1.0 documents parse. OpenAPI 2.0, 3.0.x, and 3.1.x source descriptions parse. OpenAPI 3.2 and AsyncAPI source descriptions do not yet.</li>
</ul>

<p>The feedback I want most is from people building their own Arazzo tooling: what did you have to work around, and what did you need that is not there? <a href="https://github.com/orgs/usearazzo/discussions">Discussions</a> is the place, and the <a href="https://github.com/usearazzo/arazzo-toolkit">monorepo</a> is where the issues go.</p>

<h2 id="what-comes-next">What comes next</h2>

<p>The Validator and the Runner are both in development and both unpublished. Each will get the same treatment: docs written against the code, samples run before release, a tutorial the day it lands, and a post like this one saying what it took.</p>

<p>Until then, the parser is there to be used. Start with the <a href="/docs/tutorials/list-arazzo-workflow-dependencies/">tutorial</a>, read the <a href="/docs/guides/arazzo-document-parsing/">guide</a> if you want to know what a parser has to get right, and tell me what breaks.</p>]]></content><author><name>Vladimír Gorej</name></author><summary type="html"><![CDATA[The first UseArazzo package is published, as an alpha. Why the parser went out before the Validator and the Runner, what writing its docs turned up, and what alpha means here.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://usearazzo.com/assets/images/blog/arazzo-parser-on-npm.png" /><media:content medium="image" url="https://usearazzo.com/assets/images/blog/arazzo-parser-on-npm.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">I Diffed Every Arazzo Release So You Don’t Have To</title><link href="https://usearazzo.com/blog/arazzo-specification-evolution/" rel="alternate" type="text/html" title="I Diffed Every Arazzo Release So You Don’t Have To" /><published>2026-08-29T00:00:00+00:00</published><updated>2026-08-29T00:00:00+00:00</updated><id>https://usearazzo.com/blog/arazzo-specification-evolution</id><content type="html" xml:base="https://usearazzo.com/blog/arazzo-specification-evolution/"><![CDATA[<p>When you write an Arazzo document, “which version” is a line at the top you rarely think about. When you implement Arazzo, it’s most of the job. A validator has to know exactly what 1.0.0 accepted that 1.0.1 doesn’t, and a runner has to know what a success criterion means under 1.1.0 when 1.0.1 never quite said.</p>

<p>So while building the <a href="https://github.com/usearazzo/arazzo-toolkit">UseArazzo toolkit</a>, I couldn’t rely on a rough sense of how the specification has evolved. I needed the exact diff. I downloaded the three spec texts, diffed them line by line, cross-checked every hunk against the release notes and the pull requests behind it, and kept notes as I went, because the <a href="/validator/">Validator</a> has to draw the line per version and the <a href="/runner/">Runner</a> has to honour it.</p>

<p>Those notes, tidied up, are this article. It’s for anyone who has to care what the <code>arazzo:</code> line at the top of a document means: people writing the documents, and people writing the tools that read them. If yours still say <code>arazzo: 1.0.0</code>, here is what changed since, what you need to do about it, and what you’re now allowed to say that you couldn’t before.</p>

<h2 id="at-a-glance">Three releases at a glance</h2>

<table>
  <thead>
    <tr>
      <th>Version</th>
      <th>Released</th>
      <th>Nature</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="https://spec.openapis.org/arazzo/v1.0.0.html">1.0.0</a></td>
      <td>September 2024</td>
      <td>Initial stable release</td>
    </tr>
    <tr>
      <td><a href="https://spec.openapis.org/arazzo/v1.0.1.html">1.0.1</a></td>
      <td>January 2025</td>
      <td>Patch: erratum fixes and clarifications, plus the first official JSON Schema</td>
    </tr>
    <tr>
      <td><a href="https://spec.openapis.org/arazzo/v1.1.0.html">1.1.0</a></td>
      <td>May 2026</td>
      <td>Minor: AsyncAPI v3 sources, Selector Objects, step dependencies, document identity, tightened evaluation semantics</td>
    </tr>
  </tbody>
</table>

<p>The short version: 1.0.1 removed three things that never worked and fixed the examples. 1.1.0 added a lot and, on paper, broke nothing. The rest of the post is the long version.</p>

<h2 id="from-1-0-0-to-1-0-1">From 1.0.0 to 1.0.1: the patch</h2>

<p>1.0.1 adds no objects, no fields, and no enum values. Every change is an erratum fix or a clarification. A patch release isn’t allowed to break anything, and this one doesn’t in the versioning sense: what it removes never worked. But if a document relied on one of those leftovers, removal feels like breakage, so those come first.</p>

<div class="post-callout">

  <h3 id="gotchas">Gotchas</h3>

  <ul>
    <li><strong><code>$message</code> removed from the runtime expression grammar.</strong> AsyncAPI support was dropped before 1.0.0 shipped, but <code>$message.</code> survived in the ABNF: grammatically legal, resolving to nothing. 1.0.1 removes it. It returns in 1.1.0 with real semantics.</li>
    <li><strong><code>in: body</code> removed from the Parameter Object.</strong> Request bodies are expressed through the step’s <code>requestBody</code>, so <code>in: body</code> had no defined meaning. <code>in</code> is now <code>path</code>, <code>query</code>, <code>header</code>, or <code>cookie</code>.</li>
    <li><strong><code>workflowId</code> references into an external <code>arazzo</code> source always need the runtime expression form.</strong> 1.0.0 required <code>$sourceDescriptions.&lt;name&gt;.&lt;workflowId&gt;</code> only when <em>multiple</em> <code>arazzo</code> source descriptions existed. 1.0.1 requires it whenever the referenced workflow lives in an <code>arazzo</code> source description at all. Local workflows still use the bare <code>workflowId</code>. Applies to the Step, Success Action, and Failure Action Objects. Strictly a clarification, but it’s the one that can make a previously accepted document fail a linter.</li>
  </ul>

</div>

<h3 id="changes">Changes</h3>

<ul>
  <li><strong>Step output expressions in the examples are now <code>$steps.&lt;stepId&gt;.outputs.&lt;name&gt;</code>.</strong> The 1.0.0 grammar always said <code>.outputs.</code>, but the spec’s own example wrote <code>$steps.getPetStep.availablePets</code> without it, and people copied the example. Any parser that accepted the short form was matching a spec bug. If your documents use it, they were never valid; fix them whichever version you target.</li>
  <li><strong><code>retryAfter</code> applies only to <code>type: retry</code>.</strong> 1.0.0 also mentioned <code>type: function</code>, which never existed (the allowed types are <code>end</code>, <code>retry</code>, and <code>goto</code>).</li>
</ul>

<h3 id="clarifications">Clarifications</h3>

<ul>
  <li><strong>Examples table gained JSON Pointer fragments</strong> like <code>$steps.someStepId.outputs.pets#/0/id</code> and <code>$workflows.foo.outputs.mappedResponse#/name</code>, and the “Workflow output value” note now describes payload fragments the same way step outputs do.</li>
  <li><strong><a href="https://www.rfc-editor.org/rfc/rfc7230">RFC 7230</a> and <a href="https://www.rfc-editor.org/rfc/rfc7231">7231</a> references became <a href="https://www.rfc-editor.org/rfc/rfc9110">RFC 9110</a>.</strong> Request bodies are well-defined for POST, PUT, and PATCH; on GET, HEAD, and DELETE they’re permitted but have no well-defined semantics and should be avoided. Header field names are case-insensitive.</li>
  <li><strong>A lowercase “must” became a normative MUST</strong> in the multi-document section: external documents MUST be included as a Source Description Object.</li>
  <li><strong>Typos, a misplaced parenthesis, link labels, and “runtime expression” capitalized to “Runtime Expression” throughout.</strong> No change in meaning.</li>
</ul>

<h3 id="additions">Additions</h3>

<ul>
  <li><strong>The first official JSON Schema</strong> for Arazzo. Not part of the spec text, but if you validate documents in CI with a generic JSON Schema validator, this is when that became possible. Schemas are published per minor release as dated iterations under <a href="https://spec.openapis.org/arazzo/#schema-iterations">spec.openapis.org/arazzo/</a>, and the latest date within a minor is the correct schema for every patch in it: for 1.0 that is <a href="https://spec.openapis.org/arazzo/1.0/schema/2025-10-15">2025-10-15</a>, which covers both 1.0.0 and 1.0.1 and supersedes the original 2024-12-16 iteration.</li>
</ul>

<h2 id="from-1-0-1-to-1-1-0">From 1.0.1 to 1.1.0: the minor release</h2>

<p>1.1.0 is where the specification grew. It removes no fields, no enum values, and no expression roots. Bump the version string and a 1.0.1 document is a 1.1.0 document. The categories below are ordered by how likely they are to matter to an existing document.</p>

<div class="post-callout">

  <h3 id="gotchas-1">Gotchas</h3>

  <p>A minor release can’t break existing documents, and 1.1.0 removes nothing. Two things can still change how an existing document <em>behaves</em>, because they define what 1.0 left to the implementation:</p>

  <ul>
    <li><strong>Criterion evaluation rules are pinned down further</strong> (see Changes). A runner that previously had to guess at, say, comparisons against <code>null</code> or string case may now give a different answer. The condition grammar itself is still unspecified, so not every guess went away.</li>
    <li><strong>Forward references are an error in sequential workflows.</strong> If a step references the outputs of a step that appears <em>later</em> in the <code>steps</code> array, and the workflow doesn’t use <code>dependsOn</code>, implementations should now reject it.</li>
  </ul>

</div>

<h3 id="additions-1">Additions</h3>

<ul>
  <li><strong>AsyncAPI v3 source descriptions.</strong> <code>type: asyncapi</code> joins <code>openapi</code> and <code>arazzo</code>, and the Step Object gains the fields to talk to an event channel:
    <ul>
      <li><code>channelPath</code>: a source description plus a JSON Pointer to a channel, for AsyncAPI operations that have no <code>operationId</code>. Mutually exclusive with <code>operationId</code> and <code>workflowId</code>. When the operation does have an <code>operationId</code>, the spec says to reference it that way instead, the same rule as <code>operationPath</code> for OpenAPI sources.</li>
      <li><code>action</code>: <code>send</code> or <code>receive</code>.</li>
      <li><code>correlationId</code>: for <code>receive</code> steps, which message to wait for. Must match the correlation ID in the AsyncAPI document.</li>
      <li><code>timeout</code>: milliseconds to wait.</li>
    </ul>

    <p>A <code>send</code> step completes when the message is sent; Arazzo doesn’t model broker acknowledgment. A <code>receive</code> step completes when a matching message arrives within the timeout, or fails and triggers <code>onFailure</code>. With no <code>successCriteria</code>, any matching message is success. The spec’s own example, trimmed:</p>

    <pre><code class="language-yaml">- stepId: confirmOrder
  operationId: $sourceDescriptions.asyncOrderApi.confirmOrder
  action: receive
  correlationId: $inputs.correlationId
  dependsOn:
    - placeOrder
  timeout: 6000
  outputs:
    orderId: $message.payload.orderId
</code></pre>

    <p>Scope note: 1.1.0 supports AsyncAPI <strong>v3 only</strong>. The final text links to the v3 Operations Object, <code>send</code>/<code>receive</code> is v3 terminology, and the only example is <code>asyncapi: 3.0.0</code>. Nothing addresses v2.</p>
  </li>
  <li>
    <p><strong><a href="https://spec.openapis.org/arazzo/v1.1.0.html#selector-object">Selector Object</a>.</strong> Apply JSONPath, XPath, or JSON Pointer to structured data instead of reaching for a runtime expression with a pointer suffix. <code>context</code> is a runtime expression that must evaluate to structured data, <code>selector</code> is the expression, <code>type</code> is <code>jsonpath</code>, <code>xpath</code>, <code>jsonpointer</code>, or an Expression Type Object:</p>

    <pre><code class="language-yaml">outputs:
  firstPetId:
    context: $response.body
    selector: $[0].id
    type: jsonpath
</code></pre>

    <p>Allowed anywhere a value used to be just a runtime expression: workflow <code>outputs</code>, step <code>outputs</code>, a parameter’s <code>value</code>, a request body’s <code>payload</code> (nested at any depth), and a Payload Replacement’s <code>value</code>. This is the addition I expect to see in the most documents.</p>
  </li>
  <li><strong>Step-level <code>dependsOn</code>.</strong> A list of <code>stepId</code>s that must complete before this step runs. Cross-workflow: <code>$workflows.&lt;workflowId&gt;.steps.&lt;stepId&gt;</code>. Cross-document: <code>$sourceDescriptions.&lt;name&gt;.&lt;workflowId&gt;.steps.&lt;stepId&gt;</code>. Intended as a join point for in-flight async work; for purely synchronous workflows the recommendation is still to order the array and not use it.</li>
  <li><strong><code>parameters</code> on Success Action and Failure Action Objects.</strong> An action with a <code>workflowId</code> can now hand inputs to the workflow it starts. <code>in</code> must not be used on them.</li>
  <li><strong><code>$self</code> on the Arazzo Object.</strong> A URI reference that is the document’s canonical identity and the base URI for its relative references. Must not contain a fragment; may itself be relative, resolved against the retrieval URI. If you know JSON Schema, <code>$self</code> is to an Arazzo document what <code>$id</code> is to a schema resource.</li>
  <li><strong><code>$message</code> and <code>$self</code> as runtime expression roots.</strong> <code>$message</code> sources are <code>header.</code>, <code>query.</code>, <code>path.</code>, <code>body</code>, and <code>payload</code>.</li>
</ul>

<h3 id="extended-functionality">Extended functionality</h3>

<ul>
  <li><strong>Expression Type Object</strong> (renamed from Criterion Expression Type Object, now shared by Criterion and Selector Objects): <code>type</code> gains <code>jsonpointer</code>; <code>version</code> gains <code>rfc9535</code> (<a href="https://www.rfc-editor.org/rfc/rfc9535">JSONPath</a>), <code>xpath-31</code> (<a href="https://www.w3.org/TR/xpath-31/">XPath 3.1</a>), and <code>rfc6901</code> (<a href="https://www.rfc-editor.org/rfc/rfc6901">JSON Pointer</a>), which are now the explicit defaults.</li>
  <li><strong>Payload Replacement Object</strong>: <code>target</code> may be a JSONPath as well as a JSON Pointer or XPath, and the new <code>targetSelectorType</code> says which. Defaults: JSON Pointer for <code>application/json</code>, XPath for XML media types. <code>value</code> accepts a Selector Object.</li>
  <li><strong>Parameter Object</strong>: <code>in</code> gains <code>querystring</code> (the OpenAPI 3.2 concept where the whole query string is one pre-formatted value); <code>value</code> accepts a Selector Object; <code>in</code> may be omitted whenever the step, success action, or failure action in context targets a <code>workflowId</code>.</li>
  <li><strong>Workflow and step <code>outputs</code></strong> accept a Selector Object as well as a runtime expression.</li>
  <li><strong>Runtime expressions</strong>: <code>$inputs.</code> and <code>$outputs.</code> formally allow a <code>#json-pointer</code> suffix; <code>$components.</code> covers <code>successActions</code> and <code>failureActions</code> alongside <code>parameters</code>; <code>$sourceDescriptions.&lt;name&gt;.&lt;x&gt;</code> gets a resolution order (operation or workflow ID first, then a Source Description field such as <code>url</code> or <code>type</code>); <code>{...}</code> embedding is formally defined, with serialization rules (scalars to strings, objects and arrays to JSON, strings as-is).</li>
</ul>

<h3 id="changes-1">Changes</h3>

<ul>
  <li><strong>Criterion evaluation rules.</strong> For <code>regex</code>, <code>jsonpath</code>, and <code>xpath</code> conditions, runtime expressions must be embedded as <code>{$expr}</code> and substituted before evaluation. Truthiness per type: a simple condition as written, a regex must match, a JSONPath must produce a non-empty nodelist, an XPath uses its effective boolean value. An evaluation error is a failed criterion. Multiple criteria are combined with logical AND. Simple conditions: <code>null</code> equals only <code>null</code>, numeric strings coerce to numbers, string comparison is case-insensitive. What 1.1.0 still doesn’t provide is a grammar for simple conditions: the operators are listed, but tokenization and precedence are left to the implementation, unlike runtime expressions, which get a full ABNF. I wrote about <a href="https://vladimirgorej.com/blog/the-one-arazzo-condition-no-tool-can-evaluate-safely/">one 1.0 condition no tool could evaluate safely</a> for exactly this reason; that article proposes a grammar and evaluation semantics, and adding them to the specification is proposed in <a href="https://github.com/OAI/Arazzo-Specification/issues/518">#518</a> and <a href="https://github.com/OAI/Arazzo-Specification/issues/517">#517</a>.</li>
  <li><strong><code>successCriteria</code>, if present, must contain at least one Criterion Object.</strong></li>
  <li><strong>Schema only:</strong> <code>retryAfter</code> is no longer required when <code>type: retry</code>, and a <code>retry</code> failure action no longer has to name a <code>stepId</code> or <code>workflowId</code>. Both bring the schema in line with what the prose always allowed. The 1.1 schema is a separate iteration, <a href="https://spec.openapis.org/arazzo/1.1/schema/2026-04-15">2026-04-15</a>.</li>
</ul>

<h3 id="clarifications-1">Clarifications</h3>

<ul>
  <li><strong>Step dependencies and execution order.</strong> Output references like <code>$steps.stepId.outputs.field</code> are implicit dependencies and tools must satisfy them; tools must also respect every declared <code>dependsOn</code>.</li>
  <li><strong>Defining success for asynchronous steps</strong>, as summarized under AsyncAPI above.</li>
  <li><strong>Parsing documents.</strong> A document must be parsed whole before references are resolved; parsing a fragment is undefined behaviour.</li>
  <li><strong>Identity-based referencing.</strong> A reference to another Arazzo document must use its <code>$self</code> if it has one, and absolute source <code>url</code>s resolve by identity, not location, so a document already loaded under its <code>$self</code> isn’t fetched again.</li>
  <li><strong>Base URI resolution</strong>, spelled out: <code>$self</code>, then the encapsulating entity, then the retrieval URI, then an application default. New Appendix B walks through examples. Relative references in API URLs resolve against the OpenAPI Server Object, not the Arazzo base URI.</li>
  <li><strong>Runtime expression ABNF rewritten</strong> to state formally what the prose already said: <code>$steps.</code> is <code>step-id ".outputs." output-name ["#" json-pointer]</code>, <code>$workflows.</code> likewise, plus a strict identifier form without dots.</li>
  <li><strong>Example fixes</strong>: <code>$steps.loginUser</code> to <code>$steps.loginStep</code>, <code>"{$statusCode == 401}"</code> to <code>"$statusCode == 401"</code>, JSONPath examples updated to <a href="https://www.rfc-editor.org/rfc/rfc9535">RFC 9535</a>.</li>
</ul>

<h2 id="migration-checklist">A migration checklist</h2>

<p><strong>Moving a document from 1.0.0 to 1.0.1:</strong></p>

<ol>
  <li>Change <code>arazzo: 1.0.0</code> to <code>arazzo: 1.0.1</code>.</li>
  <li>Search for <code>$message.</code> and remove it; there was nothing for it to resolve against.</li>
  <li>Search for <code>in: body</code> and move that data into the step’s <code>requestBody</code>.</li>
  <li>Search for <code>$steps.</code> references missing <code>.outputs.</code> and add it. These were never valid.</li>
  <li>If any <code>workflowId</code> points into an external <code>arazzo</code> source description, write it as <code>$sourceDescriptions.&lt;name&gt;.&lt;workflowId&gt;</code>, even if there’s only one such source.</li>
</ol>

<p><strong>Moving from 1.0.1 to 1.1.0:</strong></p>

<ol>
  <li>Change the version string. That’s the required part.</li>
  <li>If a runner evaluates your <code>successCriteria</code>, re-read them against the new condition semantics, especially anything comparing to <code>null</code> or relying on case.</li>
  <li>Then, optionally, start using what 1.1.0 gives you: Selector Objects in outputs, <code>parameters</code> on actions, <code>$self</code> on multi-document setups, and AsyncAPI v3 sources with <code>dependsOn</code> for the join points.</li>
</ol>

<p>The 1.1.0 release is three months old, and most of the documents I’ve come across still say 1.0.1. That makes now a good time to read the changes, before habits form around the old ones.</p>

<h2 id="sources">Sources</h2>

<ul>
  <li><a href="https://github.com/OAI/Arazzo-Specification/releases/tag/1.0.0">Arazzo 1.0.0</a>, <a href="https://github.com/OAI/Arazzo-Specification/releases/tag/1.0.1">1.0.1</a>, and <a href="https://github.com/OAI/Arazzo-Specification/releases/tag/1.1.0">1.1.0</a> release notes</li>
  <li><a href="https://github.com/OAI/Arazzo-Specification/compare/1.0.0...1.0.1">1.0.0 to 1.0.1 diff</a> and <a href="https://github.com/OAI/Arazzo-Specification/compare/1.0.1...1.1.0">1.0.1 to 1.1.0 diff</a></li>
</ul>]]></content><author><name>Vladimír Gorej</name></author><summary type="html"><![CDATA[Three releases in two years. A guide to what each one changed, which changes can trip up an existing document, and what 1.1.0 lets you describe that 1.0 couldn't.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://usearazzo.com/assets/images/blog/arazzo-specification-evolution.png" /><media:content medium="image" url="https://usearazzo.com/assets/images/blog/arazzo-specification-evolution.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">I Went Looking for Everything Arazzo</title><link href="https://usearazzo.com/blog/arazzo-ecosystem/" rel="alternate" type="text/html" title="I Went Looking for Everything Arazzo" /><published>2026-08-22T00:00:00+00:00</published><updated>2026-08-22T00:00:00+00:00</updated><id>https://usearazzo.com/blog/arazzo-ecosystem</id><content type="html" xml:base="https://usearazzo.com/blog/arazzo-ecosystem/"><![CDATA[<p>A couple of days ago I set out to answer a simple question: what exists around Arazzo?</p>

<p>I had a rough picture from working on the specification and on tooling, but a rough picture isn’t a list. So I went looking properly: the OAI repository, vendor blogs, YouTube, GitHub search, Slack threads, package registries, the odd LinkedIn post. I wanted to know what tools there were, who was writing about the spec, who was talking about it, and whether anyone had published a real Arazzo document for a real API.</p>

<p>It took longer than I expected, because nothing was in one place. That’s the normal state of a two-year-old specification, and it’s also the reason I kept notes. Those notes became the <strong><a href="/ecosystem/">Arazzo Ecosystem</a></strong> page.</p>

<h2 id="what-i-found">What I found</h2>

<p>More than I thought, in some places. The page currently lists 24 tools from roughly 16 vendors and independent developers, covering authoring, validation, execution, generation from OpenAPI, and libraries in TypeScript, Python, Go, Java, and PHP. The specification itself is there in all three versions, with its JSON Schemas, official examples, and the places where its development happens.</p>

<p>Articles and talks are a different story: there’s far more out there than fits in a couple of evenings. The eleven articles and four videos listed so far are simply the ones I’ve read or watched myself, and I’d rather list fewer with an accurate one-line description than paste in a hundred links I haven’t opened. That section will grow steadily.</p>

<p>What I genuinely found little of: Arazzo documents published by API providers describing their own APIs, and anything yet on 1.1, which shipped in May with AsyncAPI support.</p>

<p>I’ve kept my opinions off the page on purpose. Each entry gets a one-line description of what it is, not a rating. The tools are grouped by the job they do, so you can find a runner or a linter without knowing vendor names first.</p>

<h2 id="whats-deliberately-not-there">What’s deliberately not there</h2>

<p><a href="https://github.com/usearazzo/arazzo-toolkit">UseArazzo’s own toolkit</a>. The page is for everyone else’s work, including tools that do the same jobs ours do. A registry that left those out wouldn’t be a reference, it would be a brochure, and the Arazzo ecosystem is far too young for that to help anyone. The only competition that matters right now is with “nobody uses this.”</p>

<p>The curation policy is written at the bottom of the page and it’s short: about Arazzo, reachable at a stable URL, not a duplicate. Any vendor, no ranking, free.</p>

<h2 id="its-a-starting-point">It’s a starting point</h2>

<p>This is a first pass. I’ll keep adding things as I find them, and I’ll review the page regularly; the date is shown at the top so you can see how fresh it is.</p>

<p>But I’d rather not be the only one looking. If you’ve built something, written something, recorded something, or published an Arazzo document and it isn’t listed, <a href="https://github.com/usearazzo/website/issues/new?template=submit-a-resource.yml">submit it</a>. The form takes a minute. If something listed is wrong or gone, tell me that too.</p>

<p>If you want to know when the page changes, there’s a <a href="https://github.com/usearazzo/website/commits/main/pages/ecosystem.html.atom">commit feed</a> for now and a newsletter on the way.</p>

<p>The ecosystem is bigger than it looks from any single corner of it. <em>Now there’s a place to see the whole thing.</em></p>]]></content><author><name>Vladimír Gorej</name></author><summary type="html"><![CDATA[Now there's a place to see the whole thing.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://usearazzo.com/assets/images/blog/arazzo-ecosystem.png" /><media:content medium="image" url="https://usearazzo.com/assets/images/blog/arazzo-ecosystem.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">API Workflows Are Still Improvised. Here Is What We Are Doing About It</title><link href="https://usearazzo.com/blog/api-workflows-are-still-improvised/" rel="alternate" type="text/html" title="API Workflows Are Still Improvised. Here Is What We Are Doing About It" /><published>2026-08-18T00:00:00+00:00</published><updated>2026-08-18T00:00:00+00:00</updated><id>https://usearazzo.com/blog/api-workflows-are-still-improvised</id><content type="html" xml:base="https://usearazzo.com/blog/api-workflows-are-still-improvised/"><![CDATA[<p>I keep building API tools. This time, I’m building a home for quality Arazzo tooling.</p>

<h2 id="the-gap-openapi-leaves-open">The gap OpenAPI leaves open</h2>

<p>A single API call is a solved problem: OpenAPI describes it, generators wrap it, every HTTP client on earth can make it. But the order you call the endpoints in, what you carry from one response into the next request, and how you know you’re done? That’s been improvised. Scripts, tribal knowledge, agents winging it at runtime. If you maintain an API, test one, or build agents on top of one, you’ve written that glue yourself.</p>

<p>Improvisation is exactly what you don’t want in production. That glue is rarely reviewed, rarely versioned alongside the API it drives, and it breaks quietly when the API changes.</p>

<p>If you haven’t met it yet: <a href="https://spec.openapis.org/arazzo/latest.html">Arazzo</a> is the OpenAPI Initiative’s specification for multi-step API workflows, authored by Frank Kilcommins, and it fixes that on paper. Where OpenAPI answers “what can I call?”, Arazzo answers “in what order, with what, and until when?”. A workflow document lists its source descriptions (usually OpenAPI documents), then a set of workflows made of steps. Each step points at an operation, says which parameters and body to send, declares what counts as success, and names the outputs it makes available to later steps.</p>

<p>A minimal document looks like this:</p>

<pre><code class="language-yaml">arazzo: 1.0.1
info:
  title: Place an order
  description: Look a product up by SKU, then order it.
  version: 1.0.0
sourceDescriptions:
  - name: shop
    url: ./openapi.yaml
    type: openapi
workflows:
  - workflowId: placeOrder
    description: Two calls, in order, with the product id carried between them.
    inputs:
      type: object
      properties:
        sku:
          type: string
    steps:
      - stepId: findProduct
        description: Resolve the SKU to a product id.
        operationId: getProduct
        parameters:
          - name: sku
            in: path
            value: $inputs.sku
        successCriteria:
          - condition: $statusCode == 200
        outputs:
          productId: $response.body#/id
      - stepId: createOrder
        description: Order the product found in the previous step.
        operationId: createOrder
        requestBody:
          contentType: application/json
          payload:
            productId: $steps.findProduct.outputs.productId
        successCriteria:
          - condition: $statusCode == 201
        outputs:
          orderId: $response.body#/id
    outputs:
      orderId: $steps.createOrder.outputs.orderId
</code></pre>

<p>Nothing here is code. It’s a plain document you can check into the repository next to the OpenAPI description it depends on, review in a pull request, and hand to any tool that understands the specification. If you stop using one toolkit, the workflow keeps working with the next one.</p>

<p>And here is what comes back when you run it. Hand the document and its inputs to the Runner (more on it below) and instead of a script that just ran, you get a record of the run:</p>

<pre><code class="language-js">const result = await executor.execute('placeOrder', { inputs: { sku: 'AZ-1001' } });

result.status;   // 'completed'
result.outputs;  // { orderId: 'ord_8f2c' }
result.steps;
// [
//   { stepId: 'findProduct', successful: true, action: undefined, attempts: 1, durationMs: 142 },
//   { stepId: 'createOrder', successful: true, action: undefined, attempts: 1, durationMs: 208 },
// ]
</code></pre>

<p>Each step in run order: whether it succeeded, which success or failure action was selected, how many attempts it took, and how long. When the second call fails at 2 a.m., that’s the difference between reading a trace and reading someone’s shell script.</p>

<h2 id="the-problem-isnt-the-spec-its-the-tooling">The problem isn’t the spec, it’s the tooling</h2>

<p>Tooling for Arazzo exists. But it’s scattered across the ecosystem: a validator in one project, execution in another, nothing sharing an engine or a set of conventions. Each tool has to parse the document and resolve references its own way, so they can disagree on the edge cases.</p>

<p>So that’s the mission with UseArazzo: concentrate quality Arazzo tooling in one place, all of it sharing one engine. We’re starting with the three pieces everything else depends on.</p>

<ul>
  <li><strong><a href="/validator/">Validator</a></strong>: every specification violation, pinned to the exact location in the document that caused it, as LSP-compatible diagnostics.</li>
  <li><strong><a href="/runner/">Runner</a></strong>: executes workflows against live APIs described by OpenAPI source descriptions, step by step, and returns the trace you saw above.</li>
  <li><strong><a href="/cli/">CLI</a></strong>: validate and run in one command, on your machine and in CI. A design sketch so far. No package exists yet, and the page says so.</li>
</ul>

<p>Underneath them sit <code>@usearazzo/parser</code> and <code>@usearazzo/resolver</code>, the lower-level packages the products are built from, for people building their own Arazzo tooling. That shared engine is the point. When the Validator and the Runner disagree about a document, that’s a bug, not a difference of opinion.</p>

<p>That engine is built on <a href="https://github.com/speclynx">SpecLynx</a>, our own API tooling foundation: the semantic parsing core, reference resolution, and standards discipline that came out of years of Swagger and OpenAPI work. Between the two of us, <a href="/about/#francesco-tumanischvili">Francesco</a> and I were core contributors to the Swagger open-source projects on both the JavaScript and the Java side, and we contribute to the OpenAPI, AsyncAPI, and Arazzo specifications themselves. The <a href="/about/#our-team">About page</a> has the receipts. That’s why the toolkit handles malformed documents, external references, and specification edge cases from day one instead of learning them the hard way.</p>

<p>Three principles guide the work:</p>

<ul>
  <li><strong>Deterministic execution.</strong> A workflow that ran yesterday runs the same today. Agents get autonomy at the decision boundary, not inside the steps.</li>
  <li><strong>The standard is the product.</strong> We implement Arazzo exactly as the specification defines it. When something needs fixing, we fix it in the specification by contributing upstream, instead of adding private extensions only our tools can read.</li>
  <li><strong>Portable artifacts.</strong> Plain Arazzo documents in your repository, driven from your own code, your CI pipeline, or an agent.</li>
</ul>

<h2 id="a-word-of-honesty-before-you-click">A word of honesty before you click</h2>

<p>This is raw. Heavy development, pre-1.0, nothing published to npm yet, APIs changing under our feet. Everything lives in one monorepo, <a href="https://github.com/usearazzo/arazzo-toolkit">usearazzo/arazzo-toolkit</a>, under Apache 2.0.</p>

<p>I’m announcing it anyway. Everything is built in the open, and I’d rather have you watching, questioning, and shaping it from day one than unveil a polished black box a year from now.</p>

<p>Today the toolkit validates and runs Arazzo 1.0.0, 1.0.1, and 1.1.0 workflows against OpenAPI 2.0, 3.0.x, and 3.1.x source descriptions. AsyncAPI isn’t supported yet. The <a href="/#compatibility">compatibility table</a> on the homepage is where we keep that current, and every product page tells you exactly where that piece stands, including what doesn’t exist yet. No smoke, no mirrors.</p>

<h2 id="where-its-headed">Where it’s headed</h2>

<p>None of this is built. It’s where the toolkit is going, written down so you can tell what exists from what doesn’t.</p>

<p>Next to the three products we want a Language Service, so any editor gets Arazzo validation, completion, and navigation, and an Editor and a VS Code extension built on it. We want GitHub Actions that annotate violations on the diff and render the run trace into the job summary. We want agents to be able to author, validate, and run workflows, through Agent Skills, a generic MCP Server over any document, and an MCP Compiler that turns one workflow into one typed tool. And we want Arazzo Transformers: explicit, versioned transformations for reshaping data between steps, declared as ordinary API calls the toolchain can validate.</p>

<p>We’ll write about each of these here as it moves from idea to code.</p>

<h2 id="follow-along">Follow along</h2>

<ul>
  <li>Star and read the <a href="https://github.com/usearazzo/arazzo-toolkit">arazzo-toolkit</a> repository.</li>
  <li>Tell us what you would need from an Arazzo toolchain in <a href="https://github.com/orgs/usearazzo/discussions">Discussions</a>.</li>
  <li>Subscribe to the <a href="/feed.xml">RSS feed</a> for release notes and deeper posts on how the Validator and Runner work.</li>
  <li>Or just <a href="mailto:info@usearazzo.com">email us</a>.</li>
</ul>

<p>We’re early enough that your input actually shapes the thing.</p>]]></content><author><name>Vladimír Gorej</name></author><summary type="html"><![CDATA[OpenAPI describes endpoints, not the order you call them in. Arazzo fixes that on paper. UseArazzo is building the tooling that makes it real, in the open.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://usearazzo.com/assets/images/blog/api-workflows-are-still-improvised.png" /><media:content medium="image" url="https://usearazzo.com/assets/images/blog/api-workflows-are-still-improvised.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>