Skip to main content

Breaking Changes Guide

This guide covers how CVT detects breaking changes between API schema versions and how to use the can-i-deploy safety check.

What Are Breaking Changes?

Breaking changes are modifications to an API that can cause existing consumers to fail. CVT detects these automatically when comparing schema versions.

Types of Breaking Changes

TypeDescriptionExample
ENDPOINT_REMOVEDAn endpoint was removedDELETE /pet/{petId} no longer exists
REQUIRED_FIELD_ADDEDA new required field in request bodyRequest now requires category field
REQUIRED_PARAMETER_ADDEDNew required query/path/header param?apiVersion now required
TYPE_CHANGEDField type changed incompatiblyid changed from integer to string
RESPONSE_SCHEMA_CHANGEDResponse structure changedResponse no longer includes status field
ENUM_VALUE_REMOVEDEnum value was removedstatus no longer accepts "pending"

Non-Breaking Changes

These changes are safe for existing consumers:

ChangeWhy It's Safe
Adding optional fieldsConsumers can ignore them
Adding new endpointsExisting calls aren't affected
Adding optional parametersExisting calls work without them
Adding enum valuesExisting values still work
Relaxing validationPreviously valid requests remain valid
Improving descriptionsDocumentation-only

Schema Comparison

Using the CLI

# Compare two schema files
cvt compare --old ./v1/openapi.json --new ./v2/openapi.json

# JSON output for scripting
cvt compare --old ./v1/openapi.json --new ./v2/openapi.json --json

Output Example

✗ Found 2 breaking change(s):

[ENDPOINT_REMOVED] DELETE /pet/{petId} was removed
Old: DELETE /pet/{petId}

[REQUIRED_FIELD_ADDED] Required field 'category' added to POST /pet request
New: category (required)

Using the SDK

import { ContractValidator } from "@sahina/cvt-sdk";

const validator = new ContractValidator("localhost:9550");

// Register both versions
await validator.registerSchemaWithVersion("petstore", "./openapi-v1.json", "1.0.0");
await validator.registerSchemaWithVersion("petstore", "./openapi-v2.json", "2.0.0");

// Compare
const result = await validator.compareSchemas("petstore", "1.0.0", "2.0.0");

if (!result.compatible) {
console.error("Breaking changes detected:");
for (const change of result.breakingChanges) {
console.error(`- [${change.type}] ${change.method} ${change.path}`);
console.error(` ${change.description}`);
}
}

Deployment Safety (can-i-deploy)

The can-i-deploy check combines breaking change detection with the consumer registry to determine if a new schema version is safe to deploy.

How It Works

┌─────────────────────┐
│ New Schema v2.0.0 │
└─────────────────────┘


┌─────────────────────────────────────────────┐
│ CVT Server │
│ │
│ 1. Detect breaking changes from v1.0.0 │
│ 2. Look up registered consumers │
│ 3. Check which consumers use affected │
│ endpoints/fields │
│ 4. Return safety assessment │
└─────────────────────────────────────────────┘


┌─────────────────────┐
│ Safe / Unsafe │
│ + Affected list │
└─────────────────────┘

CLI Usage

# Basic usage
cvt can-i-deploy --schema petstore --version 2.0.0 --env prod

# With server address
cvt can-i-deploy --schema petstore --version 2.0.0 --env prod --server cvt.internal:9550

# JSON output for CI/CD
cvt can-i-deploy --schema petstore --version 2.0.0 --env prod --json

SDK Usage

const result = await validator.canIDeploy("petstore", "2.0.0", "prod");

if (result.safeToDeploy) {
console.log("Safe to deploy!");
} else {
console.error("UNSAFE:", result.summary);

// Show breaking changes
for (const change of result.breakingChanges) {
console.error(`- [${change.type}] ${change.description}`);
}

// Show affected consumers
for (const consumer of result.affectedConsumers) {
if (consumer.willBreak) {
console.error(`Consumer ${consumer.consumerId} will break!`);
console.error(
` Uses endpoints: ${consumer.relevantChanges.map((c) => c.path).join(", ")}`,
);
}
}
}

Output: Safe to Deploy

Deployment Safety Check
=======================
Schema: petstore
Version: 2.0.0
Environment: prod

✅ SAFE TO DEPLOY

No breaking changes detected that would affect registered consumers.

Output: Unsafe to Deploy

Deployment Safety Check
=======================
Schema: petstore
Version: 2.0.0
Environment: prod

❌ UNSAFE TO DEPLOY

Breaking changes in v2.0.0:
- RESPONSE_SCHEMA_CHANGED: GET /pet/{petId} response removed 'status'
- ENDPOINT_REMOVED: DELETE /pet/{petId}

Affected consumers in prod:
├── order-service v2.1.0
│ Schema version: 1.0.0
│ Impact: BREAKING
│ Affected by:
│ - GET /pet/{petId}

├── notification-service v1.5.0
│ Schema version: 1.0.0
│ Impact: BREAKING
│ Affected by:
│ - DELETE /pet/{petId}

└── billing-service v1.0.0
Schema version: 1.0.0
Impact: None


Safe consumers: 1/3
Affected consumers: 2/3

Recommendation: Coordinate with order-service and notification-service teams before deploying.

Environment Promotion

CVT tracks consumers per environment, enabling safe promotion workflows.

Development to Staging to Production

const validator = new ContractValidator("cvt.internal:9550");

// 1. Register consumer in dev after tests pass
await validator.registerConsumer({
consumerId: "order-service",
consumerVersion: "2.1.0",
schemaId: "petstore",
schemaVersion: "1.0.0",
environment: "dev", // Start in dev
usedEndpoints: [
{ method: "GET", path: "/pet/{petId}", usedFields: ["id", "name"] },
],
});

// 2. Promote to staging after dev validation
await validator.registerConsumer({
consumerId: "order-service",
consumerVersion: "2.1.0",
schemaId: "petstore",
schemaVersion: "1.0.0",
environment: "staging", // Promote to staging
usedEndpoints: [
{ method: "GET", path: "/pet/{petId}", usedFields: ["id", "name"] },
],
});

// 3. Finally promote to production
await validator.registerConsumer({
consumerId: "order-service",
consumerVersion: "2.1.0",
schemaId: "petstore",
schemaVersion: "1.0.0",
environment: "prod", // Production ready
usedEndpoints: [
{ method: "GET", path: "/pet/{petId}", usedFields: ["id", "name"] },
],
});

validator.close();

Checking Safety Before Promotion

# Before promoting petstore v2.0.0 to production
cvt can-i-deploy --schema petstore --version 2.0.0 --env prod

# Output will show if any prod consumers will break

CI/CD Integration

PR Check for Schema Changes

name: API Compatibility Check

on:
pull_request:
paths:
- "api/openapi.json"

jobs:
check-breaking-changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Get previous schema
run: git show origin/main:api/openapi.json > /tmp/old-schema.json

- name: Check for breaking changes
run: |
cvt compare --old /tmp/old-schema.json --new ./api/openapi.json

Pre-Deploy Safety Gate

deploy:
runs-on: ubuntu-latest
steps:
- name: Check deployment safety
run: |
result=$(cvt can-i-deploy \
--schema ${{ env.SCHEMA_ID }} \
--version ${{ github.sha }} \
--env prod \
--server ${{ secrets.CVT_SERVER }} \
--json)

if [ $(echo $result | jq '.safe_to_deploy') != "true" ]; then
echo "DEPLOYMENT BLOCKED: Breaking changes would affect consumers"
echo $result | jq '.affected_consumers[] | select(.will_break) | .consumer_id'
exit 1
fi

- name: Deploy
if: success()
run: ./deploy.sh

Handling Breaking Changes

When you need to make a breaking change, you have several options:

1. Coordinate with Consumers

  1. Run can-i-deploy to identify affected consumers
  2. Contact consumer teams with timeline
  3. Wait for consumers to update their code
  4. Deploy once all consumers are ready

2. Version Your API

Keep both versions available during transition:

# Old version still available
/api/v1/pet/{petId}

# New version with breaking changes
/api/v2/pet/{petId}

3. Feature Flags

Gradually roll out changes:

app.get("/pet/:petId", (req, res) => {
const pet = getPet(req.params.petId);

if (req.headers["x-api-version"] === "2") {
// New format (breaking)
res.json({ id: pet.id, details: { name: pet.name, category: pet.category } });
} else {
// Old format (compatible)
res.json({ id: pet.id, name: pet.name, status: pet.status });
}
});

4. Deprecation Period

  1. Add deprecation headers to old endpoints
  2. Set a sunset date
  3. Monitor usage metrics
  4. Remove after deprecation period

Best Practices

For API Producers

  1. Run comparisons in CI - Catch breaking changes before they're merged
  2. Use can-i-deploy before production - Make it a required gate
  3. Version your API - Major versions for breaking changes
  4. Communicate deprecations - Give consumers time to adapt

For API Consumers

  1. Register your dependencies - Enable can-i-deploy checks
  2. Specify used fields - Help producers understand impact
  3. Test against schema - Catch issues before producers deploy
  4. Monitor deprecation warnings - Plan updates proactively