The oasdiff diff command displays the diff between OpenAPI specs.
Output is fully detailed, typically in yaml or json but also available in text, markdown and html formats.
This commmand is typically used to generate a structured diff report which can be consumed by other tools but it can also be viewed by humans.
Note:
summary,breaking, andchangelogare built on the same diff engine. Most concepts and flags described here apply to those commands too. Exceptions are called out inline.
oasdiff applies a few opt-out defaults so its diff matches what most users expect from a real-world API comparison, not a literal byte-level spec comparison. Each can be disabled with --<flag>=false if your use case needs the stricter behaviour.
| Flag | Default | What it does | Detail |
|---|---|---|---|
--match-inline-refs |
true |
Match validation-equivalent inline / $ref subschemas under anyOf / oneOf as the same branch. Codegen refactors that pull an inline enum into a named component report no change. |
Matching Inline and $ref Subschemas |
--case-insensitive-headers |
true |
Compare header names case-insensitively. Content-Type and content-type are the same header per RFC 7230 / 9110. |
HEADER-DIFF.md |
--allow-external-refs |
true |
Allow the parser to resolve external $refs when loading specs. Disable when processing untrusted specs to prevent SSRF. |
(CLI help) |
All other comparison-tuning flags (--flatten-allof, --flatten-params, --include-path-params, --auto-upgrade, ...) are opt-in (default false) because they transform the input or change matching semantics in ways that not every spec wants.
The default diff output format is yaml.
Additional formats can be generated using the --format flag:
- yaml: includes all diff details
- json: includes all diff details
- text: designed to be more user-friendly and provide only the most important parts of the diff (same as markdown)
- markdown: designed to be more user-friendly and provide only the most important parts of the diff (same as text)
- html: designed to be more user-friendly and provide only the most important parts of the diff (see also changelog with html)
Notes:
- an empty
yamlorjsonresult signifies that the diff is empty, or, in other words, there are no changes. - the
jsonformat excludes theendpointssection to avoid the complex mapping keys problem.
A common way to use oasdiff diff is by running it as a step the CI/CD pipeline to detect changes.
In order to prevent changes, oasdiff diff can be configured to return an error if changes are found.
To exit with return code 1 if any changes are found, add the --fail-on-diff flag.
OpenAPI Specification has a hierarchical model of Paths and Operations (HTTP methods).
Oasdiff follows this hierarchy and displays a hierarchical diff with path changes: added, deleted and modified, and within the latter, "modified" section, another set of operation changes: added, deleted and modified. For example:
paths:
deleted:
- /register
- /subscribe
modified:
/api/{domain}/{project}/badges/security-score:
operations:
modified:
GET:Oasdiff also outputs an alternate simplified diff per "endpoint" which is a combination of Path + Operation, for example:
endpoints:
deleted:
- method: POST
path: /subscribe
- method: POST
path: /register
modified:
? method: GET
path: /api/{domain}/{project}/badges/security-score
: tags:
deleted:
- securityThe modified endpoints section has two items per key, method and path, this is called a complex mapping key in YAML.
Some YAML libraries don't support complex mapping keys, for exampple:
- python PyYAML: see #94 (comment)
- golang gopkg.in/yaml.v3 fails to unmarshal the oasdiff output. This package offers a solution: https://github.com/tliron/yamlkeys
To overcome this limitation, oasdiff allows you to exclude the endpoints section by adding the following flag: --exclude-elements=endpoints.
When using json output format, oasdiff excludes endpoints automatically.
Oasdiff tracks changes to OpenAPI extensions by default. To disable this, see Excluding Specific Kinds of Changes.
The diff format for OpenAPI extensions conforms with JavaScript Object Notation (JSON) Patch, for example:
endpoints:
modified:
? method: POST
path: /example/callback
: extensions:
modified:
x-amazon-apigateway-integration:
- oldValue: "201"
value: "200"
op: replace
from: ""
path: /responses/default/statusCode
- oldValue: http://api.example.com/v1/example/callback
value: http://api.example.com/v1/example/calllllllllback
op: replace
from: ""
path: /uri
Currently available on diff and summary.
You can use the --exclude-elements flag with to exclude one or more of the following:
- Use
--exclude-elements examplesto exclude Examples - Use
--exclude-elements extensionsto exclude Extensions - Use
--exclude-elements descriptionto exclude description fields - Use
--exclude-elements titleto exclude title fields - Use
--exclude-elements summaryto exclude summary fields - Use
--exclude-elements endpointsto exclude the endpoints section of the diff
For example, this diff excludes descriptions and examples:
oasdiff diff data/openapi-test1.yaml data/openapi-test3.yaml --exclude-elements description,examples -f text
If you want to exclude specific OpenAPI extensions by name while keeping others, use the --exclude-extensions flag.
This is different from --exclude-elements extensions which excludes ALL extensions.
For example, to exclude only x-internal and x-ignore extensions while keeping all others:
oasdiff diff base.yaml revision.yaml --exclude-extensions x-internal,x-ignore
This is useful when you have extensions that are only used internally or for tooling purposes (e.g., x-codegen-ignore, x-internal) and you don't want changes to these extensions to appear in the diff.
Under anyOf and oneOf, oasdiff treats an inline subschema and a $ref to a validation-equivalent component as the same branch by default. This catches a common codegen pattern where a tool extracts an inline schema (often an enum) into a named component without changing the accepted payloads:
# base
role:
anyOf:
- type: string
enum: [user, admin]
- type: "null"
# revision (UserRole is { type: string, enum: [user, admin] })
role:
anyOf:
- $ref: "#/components/schemas/UserRole"
- type: "null"The diff reports no change for the anyOf list because the union of accepted payloads is identical. The same behaviour applies to breaking and changelog, which run on the same diff engine.
To restore the previous behaviour where the inline branch is reported as removed and the $ref branch as added, pass --match-inline-refs=false.
Annotation-only differences (title, description, default, example, examples, external docs, $comment) are ignored for the equivalence check. Differences that affect validation (including deprecated) are not. Component $ref renames (both sides $ref, e.g. UserRoleV1 to UserRole) and inline-to-inline edits are out of scope by design: only the inline-to-$ref form change is treated as no-change.
The matching is pair-based: each deleted branch matches at most one added branch. An added $ref that happens to be equivalent to an inline branch already present on the base (a standalone addition that overlaps an existing branch, rather than a refactor) is still reported as added.
oasdiff diff data/openapi-test1.yaml data/openapi-test2.yaml
The default diff output format is yaml.
No output means that the diff is empty, or, in other words, there are no changes.
oasdiff diff data/openapi-test1.yaml data/openapi-test2.yaml -f text
The text diff report provides a simplified and partial view of the changes. It is also compatible with markdown.
To view all diff details, use yaml or json formats.
oasdiff diff data/openapi-test1.yaml data/openapi-test2.yaml -f html
The html diff report provides a simplified and partial view of the changes.
To view all diff details, use yaml or json formats.
oasdiff diff https://raw.githubusercontent.com/oasdiff/oasdiff/main/data/openapi-test1.yaml https://raw.githubusercontent.com/oasdiff/oasdiff/main/data/openapi-test3.yaml -f text
oasdiff diff "data/composed/base/*.yaml" "data/composed/revision/*.yaml" -c