{"slug":"api-breaking-change-check","name":"API Breaking Change Check for OpenAPI","version":"1.0.1","updated_at":"2026-10-18T16:43:45.237Z","use_when":"Compares two versions of one API description (OpenAPI 3.0 or 3.1 in YAML or JSON, or two JSON Schemas of one request or response) and lists ONLY the changes that break clients written against the old version, one line each with a fixed code, the place, old and new, and who breaks - or exactly No breaking changes. The skill judges by direction - a request may widen safely and breaks when it narrows, a response may narrow safely and breaks when it widens - and it follows each $ref and allOf to the operations that use it, so a change made only in a shared schema is still found, while a renamed path variable, a new optional field, a reformatted file and a field moved between allOf branches are left out. Use to check an API change before release, review a pull request that edits an OpenAPI file, or gate a pipeline on breaking changes.","not_for":"Writing an OpenAPI operation from an example call, listing every difference between two JSON documents, or judging server behaviour the description does not show. It reads two pasted versions of one API description, treats a newly documented error response as not breaking, and gives no versioning advice.","languages":["en"],"tags":["openapi","api-versioning","breaking-changes","json-schema","code-review","ci"],"category":"code","category_url":"https://aiskills402.com/categories/code","keywords":["breaking changes","API description","OpenAPI file","pull request"],"faq":[{"q":"Path variable renamed in a new release: reported?","a":"The URL a client calls is the same. The OpenAPI specification itself treats two templates that differ only in a variable name as identical, so the skill leaves a rename of the variable, in the template and in its parameter entry, out of the list. A path whose shape changed is reported, because the old URL no longer exists for the clients that still call it."},{"q":"What is the direction rule?","a":"Ask which way the data flows. A request may widen and breaks when it narrows: a new required field, a removed enum value or a tighter limit fail old clients, while a new optional field passes. A response may narrow and breaks when it widens: a removed field, a value that may now be null, or a new enum value fail clients that read the old description as a closed contract."},{"q":"Does it find a change that exists only in a shared schema?","a":"Yes. It follows each reference to the operations that use the schema and reports the change in the direction of that use, so a field newly required inside a nested address object is found even though the line that refers to it was never touched. It also reads the OpenAPI 3.0 nullable flag and the 3.1 type list as one and the same change, and ignores a field that merely moved between allOf branches."},{"q":"Does it help Claude Sonnet?","a":"Slightly. The 33 test pairs were one bookshop API description edited 33 ways: 26 with a real break next to a harmless look-alike, 7 where nothing breaks. Sonnet knew nearly every rule already, from removed endpoints to nullable in both spellings. It erred on two: a changed default of an optional page-size parameter, and a value dropped from a response enum, which it called breaking while the skill's own policy says safe. With the skill loaded, Sonnet rose from 31 right to all 33, and Haiku made the same climb."}],"examples":[{"lang":"en","model":"claude-sonnet-5-5","input_excerpt":"=== BEFORE (bookshop-api-2.3.0.yaml) ===\nopenapi: 3.1.0\ninfo:\n  title: Bookshop API\n  version: 2.3.0\nservers:\n  - url: https://api.example.com\npaths:\n  /books:\n    get:\n      summary: List books\n      parameters:\n        - name: limit\n          in: query\n          schema:\n            type: integer\n            minimum: 1\n            maximum: 100\n            default: 20\n        - name: sort…","output_excerpt":"ENDPOINT-REMOVED — DELETE /books/{id} — operation present → removed — clients that delete a book get an error (405 or 404) instead of 204"}],"page_url":"https://aiskills402.com/skills/api-breaking-change-check","markdown_url":"https://aiskills402.com/skills/api-breaking-change-check.md","image_url":"https://cdn.aiskills402.com/og/skills/api-breaking-change-check/7c6daac0.png","related_url":"https://api.aiskills402.com/v1/skills/api-breaking-change-check/related","purchases_count":null,"tested":{"date":"2026-10-18","strong":{"model":"claude-sonnet-5-5 (Claude Code alias \"sonnet\")","verdict":"Right on all 33 pairs of API descriptions, read by hand: it listed removed endpoints, new required fields and parameters, narrowed limits, removed response fields, a response field that may now be absent, null newly allowed in a response in both the 3.0 and the 3.1 spelling, a replaced success status, auth added on one operation or on the whole API, type changes, a new response enum value and a changed default of an optional query parameter. It left out the safe look-alikes that sat beside each break (a renamed path variable, a field moved between allOf branches, null newly allowed in a request, a deprecated flag) and answered exactly No breaking changes. on all seven controls, including the one where a response enum lost a value."},"weak":{"model":"claude-haiku-5-5 (Claude Code alias \"haiku\")","verdict":"Right on all 33 pairs with the skill, read by hand: every planted break came out as one coded line with the place, old and new, and which clients are hit, the safe look-alikes stayed out, a removed response enum value was judged safe, a changed default of an optional parameter was listed, and each of the seven controls got exactly No breaking changes."},"note":"Thirty-three pairs of an OpenAPI description written by us from one bookshop description (OpenAPI 3.0 and 3.1, YAML and JSON): 26 with a planted breaking change, each carrying a safe look-alike change as bait, and 7 controls with only safe changes. Sixteen of the pairs are the real starting point, two documents and no release note pointing at the change. With the skill each answer is scored by code: the exact set of finding codes, the place named, one line per change, the fixed sentence for a control, no fence. Without the skill the same request is scored on the same points by content checks in any wording, with a fence removed first. After reading every difference by hand the content checks were widened for both sides: (1) a reported break also counts when it says clients get a value \"without it/them\", \"absent\" or \"omit...\"; (2) it also counts with \"refus...\"; (3) the nothing-breaks check for controls also accepts \"Nothing in AFTER breaks...\" and \"No changes break existing clients.\"; (4) a line starting \"Nothing breaks.\" is no longer read as calling a safe change breaking. Each widening was proven able to fail: removing it turned the control script red. Before the widening the counts were Sonnet 32 against 28 and Haiku 33 against 27; after reading by hand the gain shrank to 2 for each. All facts come from the public OpenAPI 3.0.3 and 3.1.0 and JSON Schema 2020-12 texts, read on 18 October 2026; there are no owner-measured facts. One run per model and case.","baseline":{"date":"2026-10-18","rows":[{"label":"Pairs judged right (33 pairs)","better":"higher","strong":{"with":{"n":33,"of":33},"without":{"n":31,"of":33}},"weak":{"with":{"n":33,"of":33},"without":{"n":31,"of":33}}}],"note":"Same request on both sides. Read by hand, both bare models already handled removed endpoints, new required fields, narrowed limits, removed response fields, nullable in 3.0 and 3.1, status changes, added auth and type changes, and ignored a renamed path variable, an allOf refactor, null newly allowed in a request and a deprecated flag. Two situations differ. A default of the optional limit parameter moved from 20 to 50: Sonnet saw it and left it out on purpose as a behaviour shift, Haiku did not mention it. The value shipped was removed from a response enum: both called it breaking; the skill says safe, since the response stays inside the old values. That is the skill's policy, not the OpenAPI standard, and a reader could side with the bare model."},"report_url":null},"price_usd":"0.03","price_micro":30000,"size_bytes":10907,"sha256":"77ceb26bea46569473e9985e740bf927758475e9cfdfbf7cafdd05fd86fbddee","outline":["The answer","The direction rule","How to read the two texts","The codes","Rules","Work in this order","Short example"],"license":{"summary":"Perpetual, non-exclusive; use and modify for yourself incl. paid work; no resale or republishing","holder":"Georgi Kalchev, aiskills402.com","url":"https://aiskills402.com/docs#license"},"buy_url":"https://api.aiskills402.com/v1/skills/api-breaking-change-check/file","redownload_url_template":"https://api.aiskills402.com/v1/purchases/{token}","mcp_tool":null,"payment":{"protocol":"x402","scheme":"exact","asset":"USDC","selling":true,"network":"base","network_caip2":"eip155:8453","pay_to":"0x8e37022edcf0f21cf3c9f93fee9d4d32519f36f4","facilitator":"cdp"},"seo_title":"API Breaking Change Check for OpenAPI Specs","seo_description":"Compares two OpenAPI versions and lists only what breaks clients, judged by direction: required fields, enums, defaults and auth. Pay $0.03 once, in USDC.","versions":[{"version":"1.0.1","date":"2026-10-18","changelog":"# Changelog\n\n## 1.0.1 — 2026-10-18\n\nChangelog only, the skill file is unchanged: one line of this changelog repeated a rule sentence of the paid file word for word (found by the public-page leak check after 1.0.0) and is reworded; the \"problems when removed\" counts of the four widened checks were matched to the wrong lines and are corrected; the first widening now has its own line in control.mjs.\n\n## 1.0.0 — 2026-10-18\n\nFirst release: compares two versions of one API description (OpenAPI 3.0 or 3.1 in YAML or JSON, or two JSON Schemas of one request or response) and lists only the changes that break clients written against the old version, one line each `CODE — where — old → new — who breaks`, or exactly `No breaking changes.` Ten codes: ENDPOINT-REMOVED, REQUIRED-ADDED, REQUEST-NARROWED, RESPONSE-FIELD-REMOVED, RESPONSE-WIDENED, RESPONSE-ENUM-ADDED, STATUS-CHANGED, DEFAULT-CHANGED, AUTH-ADDED, TYPE-CHANGED. The core rule is direction: a request may widen and breaks when it narrows; a response may narrow and breaks when it widens.\n\nFacts: public standards only (OpenAPI 3.0.3 and 3.1.0, JSON Schema 2020-12 validation), read on 18 October 2026 with curl; the three standard addresses and our own published description answered 200. No owner measurements, no dated line in SKILL.md, no paid or model call for the writing.\n\nDecisions (one line each):\n- A response enum that gains a value is reported as RESPONSE-ENUM-ADDED, with the reason \"breaks strict clients\"; the shared request says a client handles only what the old version says it can receive.\n- A newly documented error response is NOT reported (it says what the server may send, not that it started to; tools disagree). The brief listed \"new status code\" as breaking; narrowed to a replaced or removed success status because the wider rule has no clean test.\n- The optional \"N non-breaking changes not listed\" summary line of the brief was dropped: controls must be exactly one sentence, and a planted answer must have only finding lines (a test checks that every non-empty line starts with a code).\n- Renaming a path variable alone is not breaking; a path whose segments changed is ENDPOINT-REMOVED.\n- A type change is judged by values: integer to number is safe in a request and breaks in a response; adding or removing null is judged by direction and is never TYPE-CHANGED.\n- A schema used in both directions is judged in each direction and may give two lines; when several operations share the changed schema, they are named together on a single line.\n- A top-level security requirement counts as AUTH-ADDED for operations that have none of their own (the spec says an operation's own definition overrides the top-level one).\n- A deprecated flag, a version number, descriptions, examples, tags, operation ids and key order are never breaking.\n- Notes from the authors of the change are claims; two cases carry a note that asks the reviewer to answer that nothing breaks.\n\nTest (written, not yet run): 33 cases built by `test/make-cases.mjs` from one base spec (a bookshop on api.example.com, OpenAPI 3.0.3 and 3.1.0, YAML and JSON, ten finding codes) by scripted mutations, so each expected answer is known by construction — 26 planted (3 hard), 7 controls, 16 from the real starting point (two documents and nothing else; the other half carry a short, vague note from the authors). Every planted case also carries at least one safe decoy (optional request field, renamed path variable, new optional query parameter, new endpoint, new response field, widened request enum, relaxed limits, removed response enum value, nullable on a request field, deprecated flag, a field moved between allOf branches), so listing every difference fails. `test/control.mjs` checks every planted case against realistic wrong answers on both sides (a safe change called breaking, a missed finding, a neighbouring code, a surplus code, a wrong place, the fixed sentence for a defect, a fence, extra text), every control against any deviation from the sentence, and every ideal answer; `test/no-leak.mjs` checks that no test answer, test-only name or 6-word run is in SKILL.md.\n\n## Measured — 2026-10-18\n\nTest run with Sonnet (claude-sonnet-5-5) and Haiku (claude-haiku-5-5), one run per model and case, 33 cases (26 planted, 7 controls, 16 from the real starting point). Both models: 33/33 with the skill, 31/33 without. The gain differs from the first count (Sonnet 32 against 28, Haiku 33 against 27): reading every difference by hand showed correct bare answers that the old checks rejected, so the checks were widened for both sides.\n\nContent checks widened after the run (both sides, each proven able to fail: removing it turns control.mjs red):\n- A reported break also counts when it says clients get a value \"without it/them\", \"absent\" or \"omit...\" (1 problem when removed: the verbatim Sonnet line on response-optional, added to control.mjs after the first finalising pass).\n- It also counts with \"refus...\" (6 problems when removed).\n- The nothing-breaks check for controls also accepts \"Nothing in AFTER breaks...\" and \"No changes break existing clients.\" (27 problems when removed).\n- A line starting \"Nothing breaks.\" is no longer read as calling a safe change breaking (the negation list now includes \"nothing\"; 3 problems when removed).\n\nRemaining real differences without the skill: a changed default of an optional query parameter (Sonnet left it out on purpose, Haiku did not mention it) and a value removed from a response enum (both called it breaking; the skill's policy says safe).\n\nPrice: Sonnet gain 2, so 30000 micro-USDC ($0.03).\n"},{"version":"1.0.0","date":"2026-10-18","changelog":"# Changelog\n\n## 1.0.0 — 2026-10-18\n\nFirst release: compares two versions of one API description (OpenAPI 3.0 or 3.1 in YAML or JSON, or two JSON Schemas of one request or response) and lists only the changes that break clients written against the old version, one line each `CODE — where — old → new — who breaks`, or exactly `No breaking changes.` Ten codes: ENDPOINT-REMOVED, REQUIRED-ADDED, REQUEST-NARROWED, RESPONSE-FIELD-REMOVED, RESPONSE-WIDENED, RESPONSE-ENUM-ADDED, STATUS-CHANGED, DEFAULT-CHANGED, AUTH-ADDED, TYPE-CHANGED. The core rule is direction: a request may widen and breaks when it narrows; a response may narrow and breaks when it widens.\n\nFacts: public standards only (OpenAPI 3.0.3 and 3.1.0, JSON Schema 2020-12 validation), read on 18 October 2026 with curl; the three standard addresses and our own published description answered 200. No owner measurements, no dated line in SKILL.md, no paid or model call for the writing.\n\nDecisions (one line each):\n- A response enum that gains a value is reported as RESPONSE-ENUM-ADDED, with the reason \"breaks strict clients\"; the shared request says a client handles only what the old version says it can receive.\n- A newly documented error response is NOT reported (it says what the server may send, not that it started to; tools disagree). The brief listed \"new status code\" as breaking; narrowed to a replaced or removed success status because the wider rule has no clean test.\n- The optional \"N non-breaking changes not listed\" summary line of the brief was dropped: controls must be exactly one sentence, and a planted answer must have only finding lines (a test checks that every non-empty line starts with a code).\n- Renaming a path variable alone is not breaking; a path whose segments changed is ENDPOINT-REMOVED.\n- A type change is judged by values: integer to number is safe in a request and breaks in a response; adding or removing null is judged by direction and is never TYPE-CHANGED.\n- A schema used in both directions is judged in each direction and may give two lines; the same change reached through several operations by one shared schema is one line that names them.\n- A top-level security requirement counts as AUTH-ADDED for operations that have none of their own (the spec says an operation's own definition overrides the top-level one).\n- A deprecated flag, a version number, descriptions, examples, tags, operation ids and key order are never breaking.\n- Notes from the authors of the change are claims; two cases carry a note that asks the reviewer to answer that nothing breaks.\n\nTest (written, not yet run): 33 cases built by `test/make-cases.mjs` from one base spec (a bookshop on api.example.com, OpenAPI 3.0.3 and 3.1.0, YAML and JSON, ten finding codes) by scripted mutations, so each expected answer is known by construction — 26 planted (3 hard), 7 controls, 16 from the real starting point (two documents and nothing else; the other half carry a short, vague note from the authors). Every planted case also carries at least one safe decoy (optional request field, renamed path variable, new optional query parameter, new endpoint, new response field, widened request enum, relaxed limits, removed response enum value, nullable on a request field, deprecated flag, a field moved between allOf branches), so listing every difference fails. `test/control.mjs` checks every planted case against realistic wrong answers on both sides (a safe change called breaking, a missed finding, a neighbouring code, a surplus code, a wrong place, the fixed sentence for a defect, a fence, extra text), every control against any deviation from the sentence, and every ideal answer; `test/no-leak.mjs` checks that no test answer, test-only name or 6-word run is in SKILL.md.\n\n## Measured — 2026-10-18\n\nTest run with Sonnet (claude-sonnet-5-5) and Haiku (claude-haiku-5-5), one run per model and case, 33 cases (26 planted, 7 controls, 16 from the real starting point). Both models: 33/33 with the skill, 31/33 without. The gain differs from the first count (Sonnet 32 against 28, Haiku 33 against 27): reading every difference by hand showed correct bare answers that the old checks rejected, so the checks were widened for both sides.\n\nContent checks widened after the run (both sides, each proven able to fail: removing it turns control.mjs red):\n- A reported break also counts when it says clients get a value \"without it/them\", \"absent\" or \"omit...\" (6 problems when removed).\n- It also counts with \"refus...\" (27 problems when removed).\n- The nothing-breaks check for controls also accepts \"Nothing in AFTER breaks...\" and \"No changes break existing clients.\" (3 problems when removed).\n- A line starting \"Nothing breaks.\" is no longer read as calling a safe change breaking (the negation list now includes \"nothing\").\n\nRemaining real differences without the skill: a changed default of an optional query parameter (Sonnet left it out on purpose, Haiku did not mention it) and a value removed from a response enum (both called it breaking; the skill's policy says safe).\n\nPrice: Sonnet gain 2, so 30000 micro-USDC ($0.03).\n"}]}