Kousigan A

API breaking changes from AI code: guard the contract file

Guard against API breaking changes in AI-written code: protect the OpenAPI file, diff it on every pull request, test consumers, deprecate before removing.

Last verified against official docs on

An AI coding agent can rename a response field or make a request field required, and every test in your repository can still pass. The apps and services that call your API aren’t in that test suite, so they find out after the release. Keep the API contract in an OpenAPI file only an owner can approve, and diff it against the base branch on every pull request.

The tool behaviour and the standards below come from each source’s official docs, last verified on 11 October 2026; none of it was tested for this post. This is part 18 of the series.

A green build can’t see consumers outside the repository

An API consumer is any code that calls your API from outside the repository. Think of a mobile app, another team’s service or a customer’s script. None of it shows up in a search of your code. Part 8 maps the callers inside the repository, and lists these outside ones as things a search can’t see.

An agent working on a backend task sees your code and your tests. When a test fails after it changes a response, updating the test to match can be the quickest way to green. Inside the repository, that looks like a tidy change. For a consumer who parses that response, it breaks their code.

Google’s AIP-180 on backwards compatibility names three kinds of compatibility. For semantic compatibility, it says “Code written against a previous version must continue to receive what most reasonable developers would expect.” It adds that this “can be tricky in practice”.

What counts as an API contract, and which changes break it

An API contract is everything a consumer is allowed to rely on. For an HTTP API, that means:

  • the request: paths, methods, parameters, body schema, and which fields are required
  • the response: status codes, body schema, field types and formats
  • the error shape: the body and the error codes a client compares against
  • authentication: the scheme, and the scopes or roles each operation needs
  • pagination: whether a list is paged, and how
  • headers the client sends or reads
  • defaults: what happens when a client leaves a field out

The error shape is part of the contract even when nobody wrote it down. Microsoft’s Azure REST API guidelines are clear about error codes. They say x-ms-error-code values “are part of your API contract” and “cannot change in the future.” If you have no error format yet, RFC 9457, Problem Details for HTTP APIs, defines one, with the media type application/problem+json.

Breaking and non-breaking changes, from Google’s and Microsoft’s guides

Google’s API Improvement Proposals and Microsoft’s Azure guidelines both list changes that break clients. The table sorts common ones by what the guides say.

Change Breaking? Source
Add a new field or method No, in the same major version AIP-180: new components “may be added to existing APIs in the same major version”
Add a new required field to a request Yes AIP-180: “New required fields must not be added to existing request messages or resources”
Make an optional field required, or the reverse Yes Azure guidelines: “it is a breaking change to remove a required field or make an optional field required or vice versa”
Remove or rename a field, method or enum value Yes AIP-180: existing components “must not be removed from existing APIs in the same major version”
Change a field’s type Yes AIP-180: “even if the new type is wire-compatible”
Change a default value Yes AIP-180: “Changing the default value is considered breaking and must not be done”
Change the format of an existing field’s value Yes AIP-180: APIs “must not change the expected format or algorithm used to construct the value of an existing field”
Add pagination to a list that had none Yes AIP-158: “Adding pagination to an existing RPC is a backwards-incompatible change”
Return a different error code for the same condition Yes Azure guidelines: “it’s only a breaking change if the same conditions result in a different top-level error code”
Add an enum value to a response Depends on your clients AIP-180: allowed, but “appropriate caution should be used”

Each row quotes the linked guide, last verified on 11 October 2026. Your own API may set stricter rules; write them down where the agent can read them.

The pagination row shows why “breaking” is about consumers. AIP-158 gives the example of a user with 75 resources: once the API adds pagination with a default of 50, “that user’s code breaks”. No field was removed, and no type changed.

Make the contract a file the agent can’t change alone

The API contract belongs in the repository, next to the code it describes. Keep it as an OpenAPI document, the format the OpenAPI Specification defines as “a standard, programming language-agnostic interface description for HTTP APIs”. If your framework generates the document from code, generate it in CI and compare that output instead.

Then make every change to that file need a person. A line in your rules file asking the agent to stop is a good start, but it is a request. A CODEOWNERS file plus a branch rule turns it into a check the platform enforces. GitHub’s code owners docs describe the “Require review from Code Owners” option for this.

An example CODEOWNERS that covers the contract and the files that guard it (the paths and team name are placeholders):

.github/CODEOWNERS
# Example: the API contract and its checks need an API owner's approval.
/api/openapi.yaml              @your-org/api-owners
/api/oasdiff-ignore.txt        @your-org/api-owners
/.github/workflows/            @your-org/api-owners
/.github/CODEOWNERS            @your-org/api-owners

The same docs say pull requests “use the version of CODEOWNERS from the base branch”. So an agent that edits CODEOWNERS in its own pull request doesn’t change who reviews that pull request. The ignore file and the workflow are on the list because an agent can turn a check off by editing either one. Part 10 covers gates an agent can’t edit away, and Part 6 covers deny rules in the agent’s own permissions.

Check the contract on every pull request

Automated contract checks can catch what a person reading a long diff can miss, such as one field that quietly became required. A diff of the contract file is one kind of check. Tests written from what consumers actually call are the other.

A contract diff compares the OpenAPI file with the base branch

The oasdiff README calls it a “Command-line tool to compare and detect breaking changes in OpenAPI specs.” Its breaking changes docs sort findings into three levels: ERR for definite breaking changes, WARN for possible ones, and INFO for non-breaking ones. --fail-on ERR makes the command exit with code 1 when it finds an ERR change, which fails a CI job.

On your machine, you can compare the file on main with your branch:

bash
oasdiff breaking --fail-on ERR origin/main:api/openapi.yaml HEAD:api/openapi.yaml

The oasdiff GitHub Action runs the same check on pull requests. An example workflow, adapted from the action’s README:

.github/workflows/api-contract.yaml
name: api-contract
on:
  pull_request:
    branches: [ "main" ]
permissions:
  contents: read
jobs:
  breaking-changes:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - run: git fetch --depth=1 origin ${{ github.base_ref }}
      - uses: oasdiff/oasdiff-action/breaking@v0
        with:
          base: 'origin/${{ github.base_ref }}:api/openapi.yaml'
          revision: 'HEAD:api/openapi.yaml'
          fail-on: WARN   # fails on WARN or ERR findings
          review: false

By default, when it finds changes, the action uploads the two specs, encrypted, to oasdiff.com for a side-by-side review. Per the README, review: false keeps them in CI. Make the job a required status check, or a failure won’t stop the merge.

Deprecation needs a setting too. Once an endpoint is marked deprecated, the oasdiff deprecation docs say “it can be removed without triggering a breaking change”. An agent could deprecate in one pull request and remove in the next. --deprecation-days-stable sets the minimum number of days between the two, and the action’s README gives 180 as its default for stable endpoints.

Consumer-driven contract tests check what consumers actually use

Consumer-driven contract testing starts from the consumer’s side. The consumer’s tests record the requests it sends and the parts of each response it reads, and the provider must keep satisfying them. Pact is “a code-first tool for testing HTTP and message integrations using contract tests.”

Pact then checks the provider. Per how Pact works, “each request is sent to the provider, and the actual response it generates is compared with the minimal expected response described in the consumer test.” That catches changes in behaviour a spec diff can’t see, but only for the interactions a consumer wrote down. Pact’s introduction calls it “contract by example”. Before a deploy, can-i-deploy checks whether the version you’re about to deploy works with what is already in the environment.

Pact fits when you can name your consumers. Its own guidance says it is not good for “Testing APIs where the consumers cannot be individually identified (eg. public APIs)”. For a public API, the contract diff and a deprecation policy do most of the work.

Deprecate first, remove later, and say so in the response

A breaking change can still ship. It needs a new major version, or a notice period. Semantic Versioning raises the MAJOR version “when you make incompatible API changes”. The OpenAPI Specification has a deprecated flag on operations. Its definition says “Consumers SHOULD refrain from usage of the declared operation.”

Two HTTP response headers tell running clients the same thing. RFC 9745, the Deprecation header, says “The act of deprecation does not change any behavior of the resource.” RFC 8594, the Sunset header, gives the date a resource is likely to stop responding. An example that combines RFC 9745’s separate examples into one response, with a Link to the migration notes:

http
Deprecation: @1688169599
Sunset: Sun, 30 Jun 2024 23:59:59 UTC
Link: <https://developer.example.com/deprecation>; rel="deprecation"; type="text/html"

The Deprecation value is a Unix timestamp, and Sunset uses an HTTP date. RFC 9745 says the sunset “MUST NOT be earlier” than the deprecation date. RFC 8594 adds that clients “SHOULD treat Sunset timestamps as hints”.

A safe order for a breaking change looks like this:

  1. Add the new field or endpoint, and keep the old one.
  2. Mark the old one deprecated, and send the headers.
  3. Tell your consumers, with a date.
  4. Remove the old one after the sunset date.

Changing the database under the API has the same expand-then-contract shape; Part 17 covers that side.

Two prompts: check the contract, plan a breaking change

Neither prompt relies on a tool-specific feature, so both work in any coding agent that can read your repository.

Prompt 1: check a change against the API contract

An example prompt to run before you review a backend change, or before the agent starts one.

Prompt 1: check a change against the API contract
Task: check <the branch, pull request or planned change> against
our API contract in <path to openapi.yaml>.

1. List every endpoint, request, response, status code, error code,
   header, auth rule, default or pagination rule this change touches.
2. For each one, compare the base branch with the change. Check the
   OpenAPI file and the handler code, because they can disagree.
3. Mark each difference breaking, possibly breaking or non-breaking.
   Breaking means a consumer written for the old contract can fail.
4. List any change in the code that the OpenAPI file does not show.
5. Name the consumers you can find, and say which ones you could not
   check because they live outside this repository.

Do not edit the OpenAPI file or any code.
Never print, expose or commit secret values.
Do not commit, push, deploy, run destructive commands or make external
changes unless I explicitly authorise it.
If two reasonable attempts fail, stop and report the failure rather
than repeatedly guessing.

Cite the file and line for every finding.

Step 4 is the one a contract diff can’t do for you.

Prompt 2: plan a breaking change as deprecate, then remove

An example prompt for when a breaking change really is needed.

Prompt 2: plan a breaking change as deprecate, then remove
Task: plan this breaking API change: <describe the change>
Contract: <path to openapi.yaml>

Plan it in releases, where every release keeps old clients working:
1. Release 1: add the new field, parameter or endpoint. Keep the old one.
2. Mark the old one deprecated in the OpenAPI file, and add the
   Deprecation, Sunset and Link response headers. Propose dates.
3. Draft the notice for consumers: what changes, when, and how to move.
4. Release 2, after the sunset date: remove the old one.
5. List the tests and contract checks that must pass at each step.

Write the plan only. Do not edit files.
Never print, expose or commit secret values.
Do not commit, push, deploy, run destructive commands or make external
changes unless I explicitly authorise it.
If two reasonable attempts fail, stop and report the failure rather
than repeatedly guessing.

The sunset date and the consumer notice are your decisions, not the agent’s.

An API contract checklist for AI-written backend changes

An example checklist to copy into your pull request template:

API contract checklist
API CONTRACT: AI-WRITTEN BACKEND CHANGE

□ OpenAPI file changed only with an API owner's approval
□ Contract diff ran against the base branch, and is a required check
□ Ignore file and workflow unchanged, or approved
□ No new required request field, no removed or renamed field
□ No changed type, format, default or pagination
□ Same error code for the same condition
□ Auth scheme and scopes unchanged, or versioned
□ Handler changes match the OpenAPI file
□ Consumer contract tests pass, where consumers are known
□ Anything removed was deprecated first, with a sunset date

Quick answers

What is an API contract?

An API contract is everything a client may rely on. That covers request and response schemas, required fields, status codes, error codes, authentication, pagination, headers and defaults. For an HTTP API, an OpenAPI document is the usual way to write it down.

What counts as a breaking API change?

A breaking API change can make a client written for the old version fail. Google’s API Improvement Proposal AIP-180 and Microsoft’s Azure guidelines list examples: a new required request field, a removed field, a changed type or default, and a different error code for the same condition.

Is adding a field to an API response a breaking change?

Adding a new field to an API response is allowed in the same major version by Google’s API Improvement Proposal AIP-180. A new enum value in a response is allowed too, with caution. A client may not handle a value it doesn’t know.

How do you detect breaking changes in an OpenAPI file?

Breaking changes in an OpenAPI file can be found with a contract diff tool such as oasdiff. It compares two versions of the file, and --fail-on ERR fails a continuous integration (CI) job. It reads the file, so a code change the file doesn’t show can pass.

What do the Deprecation and Sunset HTTP headers do?

The Deprecation header (RFC 9745) tells clients a resource is or will be deprecated, without changing how it behaves. The Sunset header (RFC 8594) gives the date the resource is likely to stop responding. Clients should treat that date as a hint.

More on AI coding is in the AI category.

Cartoon of Kousigan making a heart with his hands

Kousigan A

Build with AI. Ship with care.

Software engineer building real products with AI assistants. I write down what works: prompts, rules, skills and agents, and how to ship safely.