<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Semih Buğra</title>
    <description>The latest articles on DEV Community by Semih Buğra (@semihbugrasezer).</description>
    <link>https://dev.to/semihbugrasezer</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2114291%2F64c9c30f-5ad9-4b6c-baa8-23f12c0124fe.jpeg</url>
      <title>DEV Community: Semih Buğra</title>
      <link>https://dev.to/semihbugrasezer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/semihbugrasezer"/>
    <language>en</language>
    <item>
      <title>An OpenAPI diff is a judgement call, not a line diff</title>
      <dc:creator>Semih Buğra</dc:creator>
      <pubDate>Sun, 13 Sep 2026 23:46:42 +0000</pubDate>
      <link>https://dev.to/semihbugrasezer/an-openapi-diff-is-a-judgement-call-not-a-line-diff-470p</link>
      <guid>https://dev.to/semihbugrasezer/an-openapi-diff-is-a-judgement-call-not-a-line-diff-470p</guid>
      <description>&lt;p&gt;An API can return &lt;code&gt;200 OK&lt;/code&gt; and still break every client that depends on its&lt;br&gt;
contract. Uptime monitoring answers whether an endpoint responds. It does not&lt;br&gt;
answer whether the shape it responds with is still the shape your consumers&lt;br&gt;
compiled against.&lt;/p&gt;

&lt;p&gt;This is about the judgement around an OpenAPI diff: where the baseline comes&lt;br&gt;
from, how to reason about direction, what should fail automatically, and what&lt;br&gt;
still drifts after merge.&lt;/p&gt;
&lt;h2&gt;
  
  
  Start from a reproducible baseline
&lt;/h2&gt;

&lt;p&gt;Compare the proposed specification with the exact OpenAPI artifact shipped by&lt;br&gt;
the base revision. A mutable documentation URL is a poor pull-request baseline&lt;br&gt;
because it may change between two reviews of the same commit. Store or generate&lt;br&gt;
both versions so the identical pair can be reproduced locally when someone&lt;br&gt;
disputes the report.&lt;/p&gt;

&lt;p&gt;Validate both documents before diffing them. A malformed or partially generated&lt;br&gt;
spec produces a misleading compatibility report, and reviewers stop trusting the&lt;br&gt;
gate after the first false alarm.&lt;/p&gt;
&lt;h2&gt;
  
  
  Reason about direction, not diff lines
&lt;/h2&gt;

&lt;p&gt;For requests, widening what the server accepts is safer than narrowing it. For&lt;br&gt;
responses the perspective reverses: the server producing a new shape may exceed&lt;br&gt;
what an old client tolerates.&lt;/p&gt;

&lt;p&gt;Removing a response property is breaking because consumers may read it. A&lt;br&gt;
property that was always present but becomes &lt;strong&gt;optional&lt;/strong&gt; is risky in the same&lt;br&gt;
way — clients dereference it without an absence check, so the value can become&lt;br&gt;
&lt;code&gt;null&lt;/code&gt; or &lt;code&gt;undefined&lt;/code&gt; at runtime.&lt;/p&gt;

&lt;p&gt;Enum edits are directional too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;removing a value from a &lt;strong&gt;request&lt;/strong&gt; enum rejects calls that used to be valid;&lt;/li&gt;
&lt;li&gt;adding a value to a &lt;strong&gt;response&lt;/strong&gt; enum can break a consumer that compiled an
exhaustive switch with no fallback branch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A diff that assigns one severity to every enum edit will be both too loud and&lt;br&gt;
too quiet.&lt;/p&gt;
&lt;h2&gt;
  
  
  Separate what fails from what needs review
&lt;/h2&gt;

&lt;p&gt;Not every flagged change deserves the same response. Removing an operation&lt;br&gt;
should fail the build. Tightening numeric bounds, changing a string format,&lt;br&gt;
editing a default value, or dropping an optional request parameter can change&lt;br&gt;
behaviour while the JSON type stays identical — those need a human, not a red&lt;br&gt;
build. Documentation-only edits should stay visible without paging the same&lt;br&gt;
people as a deleted endpoint.&lt;/p&gt;

&lt;p&gt;Four buckets keep alerts actionable: &lt;strong&gt;breaking&lt;/strong&gt;, &lt;strong&gt;potentially breaking&lt;/strong&gt;,&lt;br&gt;
&lt;strong&gt;additive&lt;/strong&gt;, &lt;strong&gt;documentation&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  A worked example
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;GET /v1/users/{id}&lt;/code&gt; previously returned an object with &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt;. The&lt;br&gt;
proposed contract removes &lt;code&gt;email&lt;/code&gt;, adds a required &lt;code&gt;expand&lt;/code&gt; query parameter, and&lt;br&gt;
adds an optional &lt;code&gt;displayName&lt;/code&gt; response field.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;Classification&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;removed &lt;code&gt;email&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;breaking&lt;/td&gt;
&lt;td&gt;consumers may read it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;required &lt;code&gt;expand&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;breaking&lt;/td&gt;
&lt;td&gt;existing callers do not send it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;added optional &lt;code&gt;displayName&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;additive&lt;/td&gt;
&lt;td&gt;clients ignoring unknown fields are fine&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That report tells a reviewer exactly what existing clients must change. A raw&lt;br&gt;
line diff of generated YAML does not.&lt;/p&gt;
&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;

&lt;p&gt;You can check a pair of specs without installing anything permanent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @semihbugrasezer/cryonel diff base.yaml head.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It takes a repository policy file for changes a team has already accepted and&lt;br&gt;
exits non-zero on a breaking change, which is what makes it usable as a CI gate.&lt;/p&gt;

&lt;p&gt;Honest limits, because this is the part most comparison posts skip: the diff&lt;br&gt;
focuses on operations, parameters, request-body requiredness, and direct&lt;br&gt;
successful-response schemas. It reads OpenAPI 3.0 and 3.1. It does &lt;strong&gt;not&lt;/strong&gt;&lt;br&gt;
resolve remote &lt;code&gt;$ref&lt;/code&gt; URLs, and it does not replace integration tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitor drift after merge
&lt;/h2&gt;

&lt;p&gt;Pull-request checks cover planned source changes. They do not catch a gateway&lt;br&gt;
configuration edit, a generated artifact published from another repository, or a&lt;br&gt;
manual production change.&lt;/p&gt;

&lt;p&gt;Scheduled monitoring compares the deployed contract with the last known baseline&lt;br&gt;
and records &lt;em&gt;when&lt;/em&gt; the difference first appeared — which is the question an&lt;br&gt;
incident review actually asks. The document can also stay identical while&lt;br&gt;
pagination order, defaults, rate limits, or authorization policy change, so this&lt;br&gt;
narrows risk rather than eliminating it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which tool
&lt;/h2&gt;

&lt;p&gt;If you want a CLI in CI and nothing else, &lt;a href="https://github.com/Tufin/oasdiff" rel="noopener noreferrer"&gt;oasdiff&lt;/a&gt;&lt;br&gt;
is mature and I recommend it for most teams — I wrote up an&lt;br&gt;
&lt;a href="https://cryonel.com/docs/openapi-diff-tools-compared" rel="noopener noreferrer"&gt;honest comparison&lt;/a&gt; of it&lt;br&gt;
against Optic, Redocly CLI and the Atlassian tool.&lt;/p&gt;

&lt;p&gt;I build &lt;a href="https://cryonel.com" rel="noopener noreferrer"&gt;Cryonel&lt;/a&gt;, which covers two things those don't: a&lt;br&gt;
browser check with no install, and scheduled post-merge drift monitoring. The&lt;br&gt;
&lt;a href="https://cryonel.com/tools/openapi-diff" rel="noopener noreferrer"&gt;diff tool&lt;/a&gt; runs locally in the tab, and&lt;br&gt;
there's a &lt;a href="https://cryonel.com/api-guard/demo" rel="noopener noreferrer"&gt;no-signup demo&lt;/a&gt; of the&lt;br&gt;
classification if you want to see the rules before trusting them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd like feedback on
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Which classification above would you argue with?&lt;/li&gt;
&lt;li&gt;Does your team gate on contract diffs in CI today, or only review by eye?&lt;/li&gt;
&lt;li&gt;Has post-merge contract drift ever caused you an incident?&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>openapi</category>
      <category>api</category>
      <category>devops</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
