---
title: Specdiff Overview
description: Breaking-change detection for JSON Schema and OpenAPI. Compare a before and after document and get every change classified by severity, with a JSON-pointer path, a stable rule code, and a human-readable message.
url: https://pr-1-ee19382a0710.thally.app/specdiff/overview
---

# Specdiff Overview

Breaking-change detection for JSON Schema and OpenAPI. Compare a before and after document and get every change classified by severity, with a JSON-pointer path, a stable rule code, and a human-readable message.

Specdiff compares two JSON Schema or OpenAPI documents and reports every change as `breaking`, `warning`, or `info`. Each change carries a stable rule code, an RFC 6901 JSON-pointer path, and a message explaining what happened. Teams use Specdiff in CI to block breaking API changes before they merge, and coding agents call it over the Model Context Protocol before opening a pull request.

Specdiff is part of the Seamline toolkit. All packages are at version 0.1.0, require Node.js 22 or later, and are ESM only.

## Packages

Specdiff ships as three packages that share the same core engine and produce the same `DiffResult`.

| Package | Description | Runtime dependencies |
| --- | --- | --- |
| `@specdiff/core` | The differ, rule catalogue, formatters, and pointer helpers. | None (zero dependencies) |
| `@specdiff/cli` | The `specdiff` command for terminals and CI. | `@specdiff/core`, `yaml` |
| `@specdiff/mcp` | The `specdiff-mcp` stdio Model Context Protocol server for coding agents. | `@specdiff/core`, `yaml`, `@modelcontextprotocol/sdk`, `zod` |

## Installation

Install the core library for programmatic use:

```sh
npm install @specdiff/core
```

Install the CLI as a dev dependency for CI and scripts:

```sh
npm install -D @specdiff/cli
```

Or run it directly without installing:

```sh
npx -y @specdiff/cli --help
```

Set up the MCP server for coding agents by adding it to your `.mcp.json` or `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "specdiff": {
      "command": "npx",
      "args": ["-y", "@specdiff/mcp"]
    }
  }
}
```

## Library usage

Import the diff and formatter functions from `@specdiff/core`, pass in two parsed documents, and format the result.

```ts
import { diffOpenApi, formatText } from "@specdiff/core";

const result = diffOpenApi(beforeDocument, afterDocument);

console.log(formatText(result));
// Specdiff (OpenAPI): 3 changes: 1 breaking, 1 warning, 1 info
//
// Breaking changes (1)
//   BREAKING endpoint-removed  #/paths/~1owners
//            Endpoint /owners was removed.
// ...

if (result.maxSeverity === "breaking") process.exit(1);
```

For JSON Schema documents, use `diffJsonSchema` instead. For automatic detection, use `diffDocuments`, which inspects the documents and picks the right function.

```ts
import { readFileSync } from "node:fs";
import { diffDocuments, exceedsThreshold, formatMarkdown } from "@specdiff/core";

const before = JSON.parse(readFileSync("examples/user-v1.json", "utf8"));
const after = JSON.parse(readFileSync("examples/user-v2.json", "utf8"));

const result = diffDocuments(before, after, {
  ignoreRules: ["description-changed"],
  overrides: { "default-changed": "breaking" },
});

console.log(formatMarkdown(result));
console.log(exceedsThreshold(result, "warning")); // true
```

## CLI usage

The CLI accepts two file paths (JSON or YAML) and prints a change report.

```sh
npx specdiff old.yaml new.yaml
```

The command exits with code `1` when breaking changes exist (the default `--fail-on breaking` threshold). Other useful invocations:

```sh
# Markdown report, never fail
npx specdiff old.yaml new.yaml --format markdown --fail-on none

# JSON output for machine consumption
npx specdiff old.yaml new.yaml --format json --fail-on none

# Explain what a rule means and how to remediate it
npx specdiff explain required-parameter-added

# List all rules
npx specdiff rules
```

## Document kind auto-detection

Specdiff auto-detects whether it is comparing OpenAPI or JSON Schema documents. If either document contains a string-valued `openapi` key at the top level, both are treated as OpenAPI. Otherwise they are treated as JSON Schema.

You can override detection with the `--kind` flag in the CLI or by calling `diffOpenApi` or `diffJsonSchema` directly in code.

The `detectDocumentKind` function is also exported from `@specdiff/core` if you need to inspect a document yourself.

## DiffOptions

All three diff functions (`diffJsonSchema`, `diffOpenApi`, `diffDocuments`) accept an optional `DiffOptions` object.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `ignoreRules` | `RuleCode[]` | None | Rule codes to drop from the result. Changes produced by these rules are silently removed. |
| `overrides` | `Partial<Record<RuleCode, Severity>>` | None | Override the severity of specific rules. Applied after the direction table, so the override always wins. |
| `ignorePaths` | `string[]` | None | JSON-pointer prefixes. Any change whose path starts with a listed prefix is dropped. The leading `#` is optional. |
| `direction` | `"request"` or `"response"` or `"neutral"` | `"neutral"` | Sets the direction for JSON Schema diffs. Ignored for OpenAPI, where direction is derived automatically from usage context (request for parameters and request bodies, response for response bodies). |

Example with all options:

```ts
const result = diffJsonSchema(before, after, {
  ignoreRules: ["description-changed"],
  overrides: { "default-changed": "breaking" },
  ignorePaths: ["#/properties/internal"],
  direction: "response",
});
```

## DiffResult

Every diff function returns a `DiffResult` object with the following shape.

| Property | Type | Description |
| --- | --- | --- |
| `changes` | `SchemaChange[]` | All detected changes, sorted by severity (breaking first), then path, then code, then message. |
| `summary` | `DiffSummary` | Counts per severity: `breaking`, `warning`, `info`, and `total`. |
| `maxSeverity` | `Severity` or `null` | The highest severity found across all changes. `null` when there are no changes. |
| `kind` | `DocumentKind` | Either `"openapi"` or `"json-schema"`, reflecting which comparison was performed. |

Each entry in `changes` is a `SchemaChange`:

| Property | Type | Description |
| --- | --- | --- |
| `code` | `RuleCode` | A stable rule code such as `"endpoint-removed"` or `"constraint-tightened"`. |
| `severity` | `Severity` | `"breaking"`, `"warning"`, or `"info"`. |
| `path` | `string` | An RFC 6901 JSON pointer prefixed with `#`, for example `"#/paths/~1pets/get/parameters/0"`. |
| `message` | `string` | A human-readable description of the change. |
| `before` | `unknown` (optional) | The value before the change, when applicable. |
| `after` | `unknown` (optional) | The value after the change, when applicable. |

Use `exceedsThreshold(result, threshold)` to check whether the result reaches a given severity level. The threshold can be `"breaking"`, `"warning"`, `"info"`, or `"none"` (which always returns false).

## Formatters

`@specdiff/core` exports three formatters for rendering a `DiffResult`:

- `formatText(result)` produces a human-readable plain text report grouped by severity. Pass `{ color: true }` as the second argument to enable ANSI color codes.
- `formatMarkdown(result)` produces a GitHub-flavored Markdown report with summary and per-severity tables.
- `formatJson(result)` produces a pretty-printed JSON representation of the full result.

The `summaryLine(result)` function returns a one-line summary such as `"29 changes: 13 breaking, 7 warning, 9 info"` or `"No changes detected."`.