---
title: CI Integration
description: Catch breaking API changes automatically in CI/CD pipelines with Specdiff.
url: https://pr-1-ff84656b4b8a.thally.app/specdiff/ci-integration
---

# CI Integration

Catch breaking API changes automatically in CI/CD pipelines with Specdiff.

Specdiff fits into any CI/CD pipeline that can run Node.js. A typical setup compares the OpenAPI or JSON Schema spec on the base branch against the version in the pull request, and fails the build when breaking changes appear.

## GitHub Actions

The workflow below checks out the full history, extracts the base branch copy of your spec, and runs Specdiff against it. The Markdown report is appended to the GitHub step summary so reviewers see the diff directly in the pull request.

```yaml
name: API compatibility
on: pull_request
jobs:
  specdiff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - name: Extract the base branch spec
        run: git show origin/${{ github.base_ref }}:openapi.yaml > /tmp/openapi-base.yaml
      - name: Fail on breaking changes
        run: npx -y @specdiff/cli /tmp/openapi-base.yaml openapi.yaml --fail-on breaking --format markdown | tee -a "$GITHUB_STEP_SUMMARY"
```

Key points:

- `fetch-depth: 0` gives access to the base branch so `git show` can extract the old spec.
- `npx -y @specdiff/cli` downloads the CLI on the fly -- no lock file entry needed.
- `--format markdown` produces a report with tables that render natively in GitHub step summaries.
- `tee -a "$GITHUB_STEP_SUMMARY"` sends the report both to stdout and to the summary panel.

## Exit codes

Specdiff uses exit codes to communicate results to the CI runner. Any non-zero exit code fails the step by default.

| Exit code | Constant | Meaning |
|---|---|---|
| 0 | `ok` | No changes at or above the threshold. Build passes. |
| 1 | `thresholdExceeded` | Changes at or above the `--fail-on` threshold were found. Build should fail. |
| 2 | `usage` | Usage error such as an unknown flag, missing argument, or unknown rule code. |
| 3 | `inputError` | An input document could not be read or parsed. |

Exit code 1 is the expected failure mode in CI -- it means Specdiff ran successfully but found changes that exceed the configured threshold.

## Customizing the threshold

The `--fail-on` flag controls which severity level triggers exit code 1. The default is `breaking`.

```sh
# Fail on any warning or breaking change
npx -y @specdiff/cli before.yaml after.yaml --fail-on warning

# Fail on any change at all, including info-level
npx -y @specdiff/cli before.yaml after.yaml --fail-on info

# Never fail -- report only
npx -y @specdiff/cli before.yaml after.yaml --fail-on none
```

## Ignoring specific rules or paths

Use `--ignore-rule` and `--ignore-path` to suppress changes that are intentional or not relevant.

```sh
# Suppress description-changed and deprecated-added rules
npx -y @specdiff/cli before.yaml after.yaml \
  --ignore-rule description-changed \
  --ignore-rule deprecated-added

# Ignore all changes under a specific path prefix
npx -y @specdiff/cli before.yaml after.yaml \
  --ignore-path "#/paths/~1internal"
```

Both flags are repeatable. The `--ignore-path` value is a JSON pointer prefix -- the leading `#` is optional.

## Machine-readable output

Use `--format json` to get the full `DiffResult` object as JSON, which is convenient for downstream scripts or custom reporting.

```sh
npx -y @specdiff/cli before.yaml after.yaml --format json --fail-on none > diff.json
```

## Writing reports to a file

The `--output` flag (short form `-o`) writes the report to a file instead of stdout. A confirmation message is printed to stderr.

```sh
npx -y @specdiff/cli before.yaml after.yaml --format markdown --output report.md
```

This is useful in CI when you want to upload the report as an artifact or post it as a pull request comment in a separate step.

## Library-based approach

For more control, use `@specdiff/core` directly in a custom Node.js script. This lets you programmatically inspect the diff result, apply custom logic, or integrate with other tools.

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

const before = JSON.parse(readFileSync("before.json", "utf8"));
const after = JSON.parse(readFileSync("after.json", "utf8"));

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

console.log(formatMarkdown(result));

if (exceedsThreshold(result, "warning")) {
  console.error("Changes exceed the warning threshold.");
  process.exit(1);
}
```

`diffDocuments` auto-detects whether the input is OpenAPI or JSON Schema. `exceedsThreshold` returns `true` when the result contains any change at or above the given severity, so you can implement whatever pass/fail logic your pipeline needs.

You can also use `formatText` for plain-text output, or `formatJson` for the JSON representation. All three formatters accept a `DiffResult` and return a string.