---
title: @specdiff/core API Reference
description: Complete API reference for @specdiff/core — types, diff functions, result processing, rule helpers, formatters, JSON pointer utilities, ref resolution, and constants.
url: https://pr-1-ee19382a0710.thally.app/specdiff/core-api
---

# @specdiff/core API Reference

Complete API reference for @specdiff/core — types, diff functions, result processing, rule helpers, formatters, JSON pointer utilities, ref resolution, and constants.

## Installation

```bash
npm install @specdiff/core
```

`@specdiff/core` has zero runtime dependencies. It requires Node.js 22 or later and is ESM-only.

---

## Types

### Severity

```ts
type Severity = "breaking" | "warning" | "info";
```

Classifies the impact of a detected change.

### Direction

```ts
type Direction = "request" | "response" | "neutral";
```

Indicates whether a schema is used for incoming data (`"request"`), outgoing data (`"response"`), or without context (`"neutral"`). Some rules change severity depending on direction.

### DocumentKind

```ts
type DocumentKind = "openapi" | "json-schema";
```

The type of specification document being compared.

### RuleCode

```ts
type RuleCode = keyof typeof RULES;
```

A union of all 45 rule-code string literals (for example `"type-changed"`, `"endpoint-removed"`, `"property-added"`). See the **Constants** section for the full catalogue.

### FailThreshold

```ts
type FailThreshold = Severity | "none";
```

Controls whether a result should be treated as a failure. `"none"` means the result never exceeds the threshold.

### SchemaChange

```ts
interface SchemaChange {
  code: RuleCode;
  severity: Severity;
  path: string;        // RFC 6901 JSON pointer, always prefixed with "#"
  message: string;
  before?: unknown;
  after?: unknown;
}
```

A single detected change. `before` and `after` carry the relevant values when applicable.

### DiffSummary

```ts
interface DiffSummary {
  breaking: number;
  warning: number;
  info: number;
  total: number;
}
```

Aggregate counts by severity.

### DiffResult

```ts
interface DiffResult {
  changes: SchemaChange[];
  summary: DiffSummary;
  maxSeverity: Severity | null;
  kind: DocumentKind;
}
```

The output of every diff function. `changes` is sorted by severity (breaking first), then path, then code, then message. `maxSeverity` is `null` when there are no changes.

### DiffOptions

```ts
interface DiffOptions {
  ignoreRules?: RuleCode[];
  overrides?: Partial<Record<RuleCode, Severity>>;
  ignorePaths?: string[];
  direction?: Direction;
}
```

| Property | Description | Default |
|---|---|---|
| `ignoreRules` | Rule codes to drop from results. | `[]` |
| `overrides` | Override the severity of specific rules. | `{}` |
| `ignorePaths` | JSON pointer prefixes to exclude. Leading `#` is optional. | `[]` |
| `direction` | Sets the comparison context. Ignored for OpenAPI (direction is derived from usage context). | `"neutral"` |

### RuleInfo

```ts
interface RuleInfo {
  code: RuleCode;
  defaultSeverity: Severity;
  title: string;
  description: string;
  remediation: string;
  appliesTo: DocumentKind | "both";
}
```

Full metadata for a single rule in the catalogue.

### FormatTextOptions

```ts
interface FormatTextOptions {
  color?: boolean;
}
```

| Property | Description | Default |
|---|---|---|
| `color` | Enable ANSI colour codes in output. | `false` |

### Resolved

```ts
interface Resolved {
  schema: unknown;
  ref: string | undefined;
  unresolved: string | undefined;
}
```

Returned by `resolveNode`. `schema` is the resolved target. `ref` is the final `$ref` string if one was followed. `unresolved` is set when a `$ref` could not be resolved (remote or missing).

---

## Diff Functions

### diffJsonSchema

```ts
function diffJsonSchema(
  before: unknown,
  after: unknown,
  options?: DiffOptions,
): DiffResult;
```

Compares two JSON Schema documents. Supports Draft-04 through 2020-12 (structural comparison, no meta-schema validation). `options.direction` defaults to `"neutral"`.

### diffOpenApi

```ts
function diffOpenApi(
  before: unknown,
  after: unknown,
  options?: DiffOptions,
): DiffResult;
```

Compares two OpenAPI 3.x documents. `options.direction` is ignored; direction is derived from usage context (request for parameters and request bodies, response for response schemas).

### diffDocuments

```ts
function diffDocuments(
  before: unknown,
  after: unknown,
  options?: DiffOptions,
): DiffResult;
```

Auto-detects document kind and delegates to the appropriate diff function. Calls `diffOpenApi` when either document has a string `openapi` key, otherwise calls `diffJsonSchema`.

### detectDocumentKind

```ts
function detectDocumentKind(document: unknown): DocumentKind;
```

Returns `"openapi"` when `document` is an object with a string `openapi` key. Returns `"json-schema"` otherwise.

---

## Result Processing

### exceedsThreshold

```ts
function exceedsThreshold(
  result: DiffResult,
  threshold: FailThreshold,
): boolean;
```

Returns `true` when `result.maxSeverity` is at or above `threshold` in severity order (breaking > warning > info). A threshold of `"none"` always returns `false`. Empty results (no changes) never exceed.

### finalize

```ts
function finalize(
  rawChanges: readonly SchemaChange[],
  kind: DocumentKind,
  options?: DiffOptions,
): DiffResult;
```

Applies `ignoreRules`, `ignorePaths`, and severity `overrides` from the options, then sorts the changes, computes the summary, and returns a complete `DiffResult`. Useful when you build changes programmatically and want the same post-processing the diff functions apply.

### summarize

```ts
function summarize(changes: readonly SchemaChange[]): DiffSummary;
```

Computes per-severity counts from an array of changes.

### compareChanges

```ts
function compareChanges(a: SchemaChange, b: SchemaChange): number;
```

Sort comparator for changes. Orders by severity (breaking first), then path, then code, then message. Uses code-point comparison (not `localeCompare`). Pass to `Array.prototype.sort`.

---

## Rule Functions

### explainRule

```ts
function explainRule(code: string): RuleInfo | undefined;
```

Looks up a rule by its code string. Returns `undefined` for unknown codes.

### listRules

```ts
function listRules(): RuleInfo[];
```

Returns the complete catalogue of all 45 rules as an array, in catalogue order.

### isRuleCode

```ts
function isRuleCode(value: string): value is RuleCode;
```

Type guard that returns `true` when `value` is a known rule code.

### severityFor

```ts
function severityFor(code: RuleCode, direction: Direction): Severity;
```

Returns the effective severity for a rule in a given direction. If the rule has a direction-specific override in `DIRECTION_SEVERITY`, that value is returned; otherwise the rule's `defaultSeverity` is used.

---

## Formatters

### formatText

```ts
function formatText(
  result: DiffResult,
  options?: FormatTextOptions,
): string;
```

Renders a human-readable text report grouped by severity. ANSI colour codes are off by default. When there are no changes, the output includes "No changes detected." and "The documents are compatible." The returned string ends with exactly one newline.

### formatMarkdown

```ts
function formatMarkdown(result: DiffResult): string;
```

Renders a GitHub-flavoured Markdown report. Includes a heading (`## Specdiff report (OpenAPI)` or `## Specdiff report (JSON Schema)`), a summary table, and per-severity tables with `| Rule | Path | Message |` columns. Pipe characters in cell values are escaped.

### formatJson

```ts
function formatJson(result: DiffResult): string;
```

Returns the result as pretty-printed JSON (`JSON.stringify(result, null, 2)`) with a trailing newline.

### summaryLine

```ts
function summaryLine(result: DiffResult): string;
```

Returns a one-line summary string. Examples:

- `"29 changes: 13 breaking, 7 warning, 9 info"`
- `"1 change: 1 breaking, 0 warning, 0 info"`
- `"No changes detected."`

Uses singular "change" when the total is 1.

### formatRulesMarkdown

```ts
function formatRulesMarkdown(): string;
```

Returns the full rule catalogue as a Markdown table with columns `| Code | Default severity | Applies to | Description |`.

---

## JSON Pointer Helpers

Utilities for working with RFC 6901 JSON pointers. All pointers in Specdiff use the fragment form (`#/path/to/node`).

### escapePointerSegment

```ts
function escapePointerSegment(segment: string | number): string;
```

Escapes a single pointer segment per RFC 6901: `~` becomes `~0` and `/` becomes `~1`.

### unescapePointerSegment

```ts
function unescapePointerSegment(segment: string): string;
```

Reverses the escaping. `~1` is decoded before `~0`, as required by the RFC.

### joinPointer

```ts
function joinPointer(
  base: string,
  ...segments: Array<string | number>
): string;
```

Appends escaped segments to a base pointer.

```ts
joinPointer("#/paths", "/pets/{id}", "get");
// => "#/paths/~1pets~1{id}/get"
```

### parsePointer

```ts
function parsePointer(pointer: string): string[];
```

Splits a fragment pointer into decoded segments.

```ts
parsePointer("#/paths/~1pets");
// => ["paths", "/pets"]
```

Throws `Error("Invalid JSON pointer: ...")` when the pointer does not start with `/` or `#`.

### normalizePointer

```ts
function normalizePointer(pointer: string): string;
```

Normalizes various pointer forms to the canonical `#/...` form. Strips trailing slashes.

```ts
normalizePointer("paths/x");    // => "#/paths/x"
normalizePointer("/paths/x");   // => "#/paths/x"
normalizePointer("#/paths/x");  // => "#/paths/x"
```

### pointerHasPrefix

```ts
function pointerHasPrefix(pointer: string, prefix: string): boolean;
```

Segment-aware prefix check. Returns `true` only when `pointer` starts with the exact segments in `prefix`.

```ts
pointerHasPrefix("#/paths/~1petstore", "#/paths/~1pets");
// => false (different segment, not a prefix match)
```

### resolvePointer

```ts
function resolvePointer(document: unknown, pointer: string): unknown;
```

Walks the document tree following the pointer segments. Returns `undefined` when any segment is missing. Supports arrays by integer index.

---

## Ref Resolution

### isLocalRef

```ts
function isLocalRef(ref: string): boolean;
```

Returns `true` for local fragment references (`#/...` or `#`). Returns `false` for remote references.

### resolveNode

```ts
function resolveNode(
  document: unknown,
  node: unknown,
  maxDepth?: number,
): Resolved;
```

Follows chains of local `$ref` pointers starting from `node`. Stops after `maxDepth` hops (default 32). Returns a `Resolved` object:

- `schema` -- the resolved target schema.
- `ref` -- the final `$ref` string that was followed, or `undefined` if no `$ref` was present.
- `unresolved` -- set when the `$ref` could not be resolved (remote reference, missing target, or depth exceeded).

---

## Constants

### SEVERITY_ORDER

```ts
const SEVERITY_ORDER: {
  readonly breaking: 0;
  readonly warning: 1;
  readonly info: 2;
};
```

Numeric ordering of severities. Lower numbers represent higher severity.

### SEVERITIES

```ts
const SEVERITIES: readonly ["breaking", "warning", "info"];
```

All severity values in order from most to least severe.

### RULES

```ts
const RULES: Record<RuleCode, RuleInfo>;
```

The complete catalogue of 45 rules. Each entry contains the rule's `code`, `defaultSeverity`, `title`, `description`, `remediation`, and `appliesTo` field.

**JSON Schema rules** (also fire inside OpenAPI schemas):

| Code | Default Severity | Description |
|---|---|---|
| `type-changed` | breaking | `type` changed, added, or removed (ignoring null) |
| `property-removed` | breaking | Property removed from `properties` or `patternProperties` |
| `property-added` | info | New optional property added |
| `required-property-added` | breaking | New property added that is also in `required` |
| `required-added` | breaking | Existing property added to `required` |
| `required-removed` | info | Property removed from `required` |
| `enum-value-removed` | breaking | Value removed from `enum` |
| `enum-value-added` | info | Value added to `enum` |
| `additional-properties-restricted` | breaking | `additionalProperties` became more restrictive |
| `additional-properties-relaxed` | info | `additionalProperties` became more permissive |
| `constraint-tightened` | breaking | Validation keyword became stricter |
| `constraint-relaxed` | info | Validation keyword became more permissive |
| `format-changed` | warning | `format` added, removed, or changed |
| `nullable-removed` | breaking | Null no longer accepted |
| `nullable-added` | info | Null now accepted |
| `default-changed` | warning | `default` added, removed, or changed |
| `description-changed` | info | `description` text changed |
| `composition-variant-removed` | breaking | Subschema removed from `oneOf`/`anyOf`/`allOf` |
| `composition-variant-added` | info | Subschema added to `oneOf`/`anyOf`/`allOf` |
| `items-changed` | breaking | `items` or `prefixItems` added, removed, or shape changed |
| `const-changed` | breaking | `const` value added, removed, or changed |
| `deprecated-added` | warning | `deprecated: true` added |
| `readonly-writeonly-changed` | warning | `readOnly` or `writeOnly` changed |
| `unresolved-ref` | warning | `$ref` could not be resolved |

**OpenAPI-only rules:**

| Code | Default Severity | Description |
|---|---|---|
| `endpoint-removed` | breaking | Path removed from `paths` |
| `endpoint-added` | info | New path added |
| `operation-removed` | breaking | HTTP method removed from existing path |
| `operation-added` | info | New HTTP method added |
| `operation-id-changed` | warning | `operationId` changed |
| `parameter-removed` | breaking | Parameter removed (matched by name and location) |
| `required-parameter-added` | breaking | New required parameter added |
| `optional-parameter-added` | info | New optional parameter added |
| `parameter-required-changed` | breaking | Parameter `required` flag changed |
| `request-body-required-added` | breaking | Request body gained `required: true` |
| `request-body-media-type-removed` | breaking | Media type removed from request body content |
| `request-body-media-type-added` | info | Media type added to request body content |
| `response-removed` | breaking | Status code removed from responses |
| `response-added` | info | New status code documented |
| `response-media-type-removed` | breaking | Media type removed from response content |
| `response-media-type-added` | info | Media type added to response content |
| `security-requirement-added` | breaking | New security scheme required or anonymous access removed |
| `security-requirement-removed` | info | Security scheme removed |
| `server-removed` | warning | URL removed from top-level `servers` |
| `server-added` | info | URL added to `servers` |
| `deprecated-operation` | warning | Operation gained `deprecated: true` |

### DIRECTION_SEVERITY

```ts
const DIRECTION_SEVERITY: Partial<Record<RuleCode, Record<Direction, Severity>>>;
```

Contains 12 entries for rules whose severity changes with direction. Rules not listed here keep their `defaultSeverity` in all directions.

| Rule | request | response | neutral |
|---|---|---|---|
| `required-added` | breaking | info | breaking |
| `required-property-added` | breaking | info | breaking |
| `required-removed` | info | breaking | info |
| `enum-value-added` | info | warning | info |
| `enum-value-removed` | breaking | info | breaking |
| `constraint-tightened` | breaking | info | breaking |
| `constraint-relaxed` | info | warning | info |
| `additional-properties-restricted` | breaking | info | breaking |
| `nullable-added` | info | breaking | info |
| `nullable-removed` | breaking | info | breaking |
| `composition-variant-added` | info | warning | info |
| `composition-variant-removed` | breaking | info | breaking |

---

## Usage Examples

### Compare two OpenAPI documents

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

const result = diffOpenApi(beforeDocument, afterDocument);
console.log(formatText(result));

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

### Compare with options and threshold checking

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

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

console.log(formatMarkdown(result));
console.log(exceedsThreshold(result, "warning")); // true if any warning or breaking
```