Breaking changes and how to avoid causing one
The rule is simple: adding is safe, removing and renaming are not.
A breaking change is any change that makes previously valid usage stop working. The critical thing to internalise is that you often cannot fix it by deploying again, because the broken caller is a mobile app on someone’s phone, or a script you have never heard of.
Safe to do
- Add a new optional field to a response
- Add a new endpoint
- Accept a new optional input
- Make an error message clearer
Breaks callers
- Remove or rename a field
- Make an optional input required
- Change a field’s type, even subtly
- Change what a status code means
The universal escape hatch is the same shape as the safe migration you saw in the Data track: add the new thing, support both for a while, watch until the old one stops being used, then remove it. Versioning (/v1 and /v2) is that idea applied to an entire API at once.
Who is still calling the old version, and how would I know?
Without an answer, "we removed it, nobody was using it" is a hope. Logging usage of deprecated fields turns that hope into a date you can act on.
What to remember
- Adding is safe; removing, renaming and retyping are not.
- You cannot deploy your way out of breaking a client you do not control.
- Support both versions, measure usage, then remove.
Terms in this lesson
Field notes
Loaded from a deliberately slow source. The lesson above was already readable while this was still travelling. That is streaming, and it is the same trick a chat interface uses.
The URL that read everyone’s invoices
An invoice page checked that you were logged in and then loaded whatever id was in the address. Changing the number showed somebody else’s invoice. It was found by a customer who mistyped, not by a review.
Classic IDOR, still extremely common
The key in the client bundle
An API key was placed behind the public environment prefix so it would be readable from the frontend. It worked. It was also visible to every visitor, and was being used by strangers within a week.
Scraped from a public bundle
resolved in 900ms · region iad1
Hide field notes toggles a search param the loader reads. With it off, the slow promise is never created, so nothing streams.