Your API Contract Is a Promise. Stop Breaking It.

by Arif Ikhsanudin, Backend Developer

The Breaking Change Nobody Announced

Your team renamed a JSON field from userId to user_id to match your new naming convention. The change was "internal," and anyway the consumer team would update their code — they were CC'd in the PR. Except they weren't actively monitoring that PR. Their service went to production two days later, calling your API, and started failing silently because userId now returned null. The bug took four hours to trace.

This is a breaking change. Not a controversial one, not an edge case — by any definition of the term. userId existed in your contract and now doesn't. Every caller depending on it is broken.

The fact that it was unintentional doesn't change the consequence.

What Constitutes a Breaking Change

Breaking changes are more numerous than most teams recognize. They are not limited to removing fields or changing endpoints. A breaking change is any change that causes a valid request that previously worked to either fail or produce a different result.

Backward-incompatible changes include:

  • Removing a field from a response
  • Renaming a field (even to a "better" name)
  • Changing a field's type (string to integer, nullable to required)
  • Adding a required field to a request body
  • Changing the semantic meaning of an existing field
  • Removing an enum value that callers may be matching against
  • Tightening validation (rejecting inputs that were previously accepted)
  • Changing error codes that callers may be handling

Backward-compatible changes (safe to make without a version bump):

  • Adding an optional field to a response
  • Adding an optional field to a request body
  • Adding a new endpoint
  • Adding a new enum value (callers must handle unknown values gracefully)
  • Relaxing validation (accepting inputs previously rejected)

Versioning Strategies

There are three common approaches, each with real tradeoffs:

URI versioning (/v1/orders, /v2/orders): Explicit, visible in logs, easy to route. Leads to proliferation of versioned endpoints that need to be maintained in parallel. The most common approach for public APIs.

Header versioning (Accept: application/vnd.myapi.v2+json or a custom API-Version: 2 header): Keeps URIs clean. Harder to test manually (must set headers on every request), less visible in logs. Common in REST-purist environments.

Query parameter versioning (/orders?version=2): Simple to implement. Generally considered a weaker signal of intent — query parameters are supposed to filter, not route.

Pick one and be consistent. Mixing strategies in the same API is worse than any one strategy imperfectly applied.

The Deprecation Protocol That Actually Works

A contract isn't just about new versions — it's about how long old versions live. A deprecation protocol removes ambiguity:

  1. Announce deprecation with a concrete end-of-life date — not "sometime next quarter"
  2. Add a Deprecation response header and a Sunset header (RFC 8594) on every response from the deprecated endpoint: Sunset: Sat, 31 Dec 2026 23:59:59 GMT
  3. Monitor call volume to the deprecated endpoint. If it's still at 10% of its peak at the end-of-life date, the date may need to move
  4. Give consumers a migration guide, not just a changelog

The Sunset header is machine-readable. Clients can programmatically detect that an endpoint is being retired and alert their teams. This is better than email threads.

Consumer-Driven Contract Testing

The most robust technical approach to preventing breaking changes is consumer-driven contract testing with Pact. Here's how it works:

  • Each consumer service defines the requests it makes and the response fields it depends on (not the full response, just the parts it uses)
  • These "pacts" are published to a Pact Broker (a service that stores and manages contracts)
  • The provider's CI pipeline verifies that the current provider code satisfies all published pacts before merging or deploying
Consumer A pact: GET /orders/{id} must return { id, status, totalAmount }
Consumer B pact: GET /orders/{id} must return { id, customerId }
Provider CI: run pact verification -> confirm both contracts are satisfied

If you rename totalAmount to total, Consumer A's pact fails in the provider's CI pipeline before the change reaches production. You catch the breaking change before it breaks anything.

This is the correct answer to "how do we prevent accidental breaking changes in a microservices environment."

The Practical Takeaway

Before your next API change, run through the breaking change checklist above. If any item applies, you have two options: create a new API version or find a backward-compatible way to make the change. "We'll notify them" is not a third option — it's a process that will fail at least once and cause a production incident. The tooling exists to do this correctly. Use it.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

Why Building Software Is More Expensive Than Most Founders Expect

Wait… why is this so expensive? It’s just an app.” That moment hits almost every founder at some point.

Read more

Employee vs Contractor: The Real Financial Difference

Why that “expensive” contractor rate isn’t as simple as it looks (and why employees aren’t as cheap as they seem)

Read more

Why Your Developers Are Burning Out

Your developers are working late nights, skipping breaks, and looking exhausted. Burnout isn’t a personal failure—it’s a signal that something in the system is broken.

Read more

Least Privilege in Docker: Why It Matters for Backend Apps

Least privilege is not an abstract security principle — it's a concrete set of Docker configuration choices that limit what an attacker can do if they get code execution in your container. Most default configurations fail this check.

Read more