How it works

Anatomy of a breaking change: one pull request, three teams, two alerts

Last checked 2026-11-1410 min read

A breaking API change is a change to an API’s contract that can make an existing client fail, such as a removed response field or a new required parameter. This page follows one example pull request through Graphwake: the CI step sends the changed OpenAPI spec as a draft, Graphwake finds two breaking changes, scores the change set 54/100 with four public rules, and alerts the two teams subscribed to the endpoint in their own channels. A third team that calls the same service, but not that endpoint, hears nothing. After the merge, the change and the teams told are kept in the change history.

ExampleService graph · orders-api
GET/orders/{id}2 consumer teams
Service
orders-api
Owner
payments-team

Consumers

  • checkout · 4 peopleSlack #team-checkout
  • mobile-app · 6 peopleSlack #mobile-alerts

Suggested by Discovery

  • storefront · uses a neighbouring endpointmaybe · 72
GET/orders/{id}/invoiceBlind spot

No team subscribes to this endpoint. A breaking change here reaches no subscribed team.

Who owns an endpoint, who consumes it, and where nobody is listening. A suggested team is added only when someone confirms it.
On this page
  1. The cast
  2. The timeline
  3. 1. The spec changes in a pull request
  4. 2. The CI step sends the spec as a draft
  5. 3. Graphwake finds the breaking changes
  6. 4. The impact score is computed
  7. 5. Two teams are alerted, the third is not
  8. 6. The teams prepare, then the pull request merges
  9. 7. The change history keeps the record
  10. Why routing beats broadcasting
  11. How Graphwake does it
  12. What to take from this
  13. Questions

The cast

The behaviour is Graphwake’s; the company, people and numbers are made up. Four teams are involved, though only one of them writes the change:

ExampleWhoRelationship to GET /orders/{id}
payments-teamOwns orders-api. John is on this team and opens the pull request.Owner
checkout4 people. Calls the endpoint to show an order total at checkout. Alerts go to Slack #team-checkout.Subscribed consumer
mobile-app6 people. Calls it from the order details screen. Alerts go to Slack #mobile-alerts.Subscribed consumer
searchCalls GET /orders to index orders, never GET /orders/{id}.Consumes the service, not this endpoint

The visual at the top of this page shows that map for GET /orders/{id}. It comes from subscriptions: each consumer team marked the endpoints it calls. Graphwake uses that list to tell checkout and mobile-app and to leave search alone, so nobody has to remember who calls what during a review. It also shows storefront, a Discovery suggestion nobody has confirmed, and an endpoint with no subscriber.

The timeline

From pull request to alertA spec change in a pull request is sent as a draft by the CI step. A breaking change is found and an impact score is calculated. Two subscribed teams are alerted and one non-subscribed team is not. After the merge, the change goes into the change history.Spec changed in a pull requestCI step sends the draftBreaking change foundImpact score calculated2 subscribed teamsalerted1 non-subscribed teamnot alertedno alertPull request mergedRecorded in change history
Example: pull request #42 in orders-api, from the spec change to the change history.

1. The spec changes in a pull request

John renames the response field total to totalAmount and adds a region query parameter so orders can be served from the nearest region. He makes region required, because the new code needs it. The spec change in his pull request looks like this:

yaml
# orders-api/openapi.yaml, pull request #42 (branch orders-region-param)
paths:
  /orders/{id}:
    get:
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
+       - { name: region, in: query, required: true, schema: { type: string } }
      responses:
        "200":
          content:
            application/json:
              schema:
                properties:
                  id:          { type: string }
-                 total:       { type: number }
+                 totalAmount: { type: number }
                  currency:    { type: string }
Example: the OpenAPI change in pull request #42. Lines starting with + were added and the line starting with - was removed.

As a code diff, this is a small pull request. As a contract it holds three changes, and two of them break callers that work today.

2. The CI step sends the spec as a draft

orders-api runs the Graphwake CI agent in its pipeline. On the pull request build, the agent sends the spec with the build’s context: pull request #42, its link, the author and the commit. Graphwake stores it as a draft for that pull request. The live spec stays exactly as it was, because nothing has been merged.

Because of this step, everything that follows happens before the merge. Without it, Graphwake can still poll a published spec URL every 30 minutes, but then it only sees the change after it is deployed.

3. Graphwake finds the breaking changes

Graphwake compares the draft with the live spec, endpoint by endpoint. We call a change breaking when it can make an existing client fail, which in practice means one of these:

  • an endpoint or a response field is removed;
  • a new required parameter or required request field is added;
  • a field’s type changes;
  • the accepted values are narrowed, for example an enum loses a value.
Example change in #42Breaking?Why
Response field total removedYesEvery client that reads total now gets nothing. A rename is a removal plus an addition.
Query parameter region added as requiredYesEvery existing request leaves it out, and the server can now reject those requests.
Response field totalAmount addedNoClients ignore fields they do not know. It stays quiet by default.

The rule of thumb behind the list is whether a client that follows today’s contract keeps working. Adding something a client can ignore is safe. Taking away something it reads, or demanding something it does not send, breaks it. A team that wants to hear about the safe changes too can opt in to every change.

4. The impact score is computed

Each change set gets a score from 0 to 100, made of four parts with public rules. Each part counts something, gives a fixed number of points per item and stops at a cap, so no single part can dominate. The impact score page has the full rules. For #42:

RuleCountPoints eachCapPoints
Breaking changes2103020
Affected teams2123624
People to notify1011410
Blind spots010200
Total54
  • Breaking changes: 2. The removed field and the new required parameter. The added field does not count.
  • Affected teams: 2. checkout and mobile-app subscribe to GET /orders/{id}. search does not, so it does not count.
  • People to notify: 10. The distinct members of the affected teams: 4 in checkout and 6 in mobile-app, nobody in both. Someone who follows the endpoint personally gets the alert by email but is not counted here.
  • Blind spots: 0. Every endpoint with a breaking change has at least one subscriber, and no endpoint was removed.

54/100 is Medium. Low runs from 0 to 29, Medium from 30 to 59, and High is 60 and above. We publish the rules so that a reviewer can check every point.

Blind spots work differently. Suppose #42 had also removed GET /orders/{id}/invoice, which no team subscribes to. That is a third breaking change (30 instead of 20) and a blind spot (+10): 74/100, High. The score goes up because Graphwake has nobody to tell. When that happens, Discovery suggests teams that may be affected, each with a reason, and a suggested team becomes a subscriber only when someone confirms it.

The visual below is not pull request #42. It shows a larger Example, pull request #1284 in the same service, with three breaking changes and three affected teams, to show what a High score looks like: 30 + 36 + 13 people = 79/100.

ExamplePull request #1284 · orders-api · not merged
79/100Highbefore the merge
3 breaking changes
10 each, up to 30
30
3 consumer teams affected
12 each, up to 36
36
13 people notified
1 each, up to 14
13
Blind spots
10 each, up to 20
0
Low0-29Medium30-59High60+
Four public rules add up to the score, so a reviewer can check every point of it.

5. Two teams are alerted, the third is not

With “Alerts on open pull requests” turned on in settings, Graphwake sends the alert while #42 is still open. It goes to the teams subscribed to the changed endpoint, each in the channel that team chose: checkout in #team-checkout, mobile-app in #mobile-alerts. A team that chose Microsoft Teams or email gets the same information there.

text
Breaking change in PR #42
Service: orders-api
Endpoint: GET /orders/{id}
Owned by payments-team
Change: <the removed field>; <the new required parameter>
Author: john
PR: https://github.com/example/orders-api/pull/42
Example: the lines of the chat message checkout and mobile-app get. The Change line holds a short description of each breaking change found, separated by semicolons.

The message carries what a consumer needs to act on it: the endpoint, what changed, who changed it and where to comment. The impact score stays in Graphwake, on the change set, next to the list of teams it reached.

search gets nothing. It calls orders-api, but not the endpoint that changed. In Graphwake’s impact view it is listed among the teams that consume the service but none of the changed endpoints, so the producer can see that it was considered and that skipping it was deliberate.

While the pull request stays open, Graphwake keeps one message per team and endpoint. If John pushes again, the existing message is edited in place instead of a new one being posted. An edit notifies nobody, so if a push makes a change breaking that was not before, Graphwake adds a thread reply: “This change is now breaking.” If a push removes the breaking change, the message is updated to say it is no longer breaking.

6. The teams prepare, then the pull request merges

checkout starts sending region, mobile-app moves to totalAmount, and each team marks the change as handled for itself in Graphwake. That conversation happened during the review. Usually it happens after an incident.

John merges #42. The default-branch build sends the spec as live, and the agent reports that the commit came from pull request #42. Graphwake settles the draft as merged: each team’s message is updated to “Published: Breaking change in PR #42”, with the thread reply “Published to live.” The handled marks carry over, because the breaking changes were the same in the draft and at the merge. Had John closed the pull request instead, the reply would have been “PR closed without merging.”

7. The change history keeps the record

The merge is recorded in the change history of orders-api and of GET /orders/{id}: the change set, its breaking changes, the team that published it, and which teams were told in which channel. The history is append-only. Months later, when someone asks why checkout knew about the region parameter, checkout’s subscription to the endpoint links to the change it was told about.

If an old pipeline on the main branch is re-run later, its spec does not quietly undo #42: Graphwake ignores a live spec from an older commit than the one it already holds.

Why routing beats broadcasting

The usual alternative is a post in a shared channel, and the hope that everyone who calls the endpoint reads it. In this example that means asking the whole company to work out whether they call GET /orders/{id}.

  • With subscriptions, each consumer team records what it calls once, when it starts calling it. Nobody has to repeat that matching every time another team ships.
  • search heard nothing in this example, and that tells search something: it is not affected. In a broadcast channel, hearing nothing only means you missed the post.
  • An endpoint nobody subscribes to shows up as a blind spot and raises the score, where a broadcast would leave it to surface in production.

More on this in stop announcing API changes in Slack and API breaking change alerts.

How Graphwake does it

  • Before the merge

    The CI step sends the pull request’s spec as a draft, so consumer teams can hear about a breaking change while it is still under review.

  • Only the subscribed teams

    Consumer teams subscribe to the endpoints they call. A breaking change goes to those teams in Slack, Microsoft Teams or email, not to a company-wide channel.

  • A score you can check

    Each change set gets a score from 0 to 100 from four public rules, shown line by line in Graphwake.

  • A record that stays

    Merges, the teams told and their channels are kept in an append-only change history for each service and endpoint.

See who your next breaking change reaches.Paste a spec URL or add one CI step. 7-day free trial. Cancel before day 7 and you pay nothing.
Start free trial

What to take from this

  1. Review spec changes as contract changes. A rename is a removal, and a new required input breaks every caller that does not send it.
  2. Count who is affected before the merge, not after. Two breaking changes on an endpoint with two consumer teams is a different conversation from the same changes on an endpoint nobody calls.
  3. Tell the teams that call the endpoint, in their own channel, and let everyone else stay quiet.

For the review side, see the API breaking change checklist. For more examples of what counts as breaking, see OpenAPI breaking changes.

Questions

What makes an OpenAPI change breaking?

A change that can make an existing client fail: removing an endpoint or a response field, adding a required parameter or request field, changing a field’s type, or narrowing the accepted values. Adding an optional field is not breaking, and Graphwake stays quiet about it by default.

Where do I see the impact score of a pull request?

In Graphwake, on the change set: the score, the four lines that add up to it, the teams affected and the channel each one uses. The alert itself names the endpoint, what changed, who changed it and the pull request.

Is the team that owns the endpoint alerted?

Alerts follow subscriptions. The message names the owner (“Owned by payments-team”), and the owner team gets the alert if it also subscribes to the endpoint.

What if our spec reaches Graphwake by URL instead of CI?

Graphwake checks the published spec URL every 30 minutes and runs the same steps on what changed: breaking changes, score, alerts to subscribed teams, change history. There is no pull request draft, so the alert comes after the change is published.

What happens if the pull request is closed without merging?

On GitHub, the teams that were told get a thread reply, “PR closed without merging.”, and the draft is closed. Where the CI does not report closes, the draft closes quietly after 14 days. The live spec never changed.

Pick an endpoint. See who it reaches. 7-day free trial. Cancel before day 7 and you pay nothing.

Start free trial