Quick answer: A change is backwards compatible only when a client that knows nothing about it keeps working, so test it by sending the old request against the new server and confirming every old field behaves exactly as before. Add the new field in a separate case, and keep the two directions distinct: the new client using the new field, and the old client ignoring it. The failures that slip through are not deletions, they are changes that look additive, such as a new enum value or a shifted default, that a strict parser rejects.

What additive actually means

An additive change adds without removing or redefining: a new optional request parameter, a new response field, a new endpoint, or a new header. The new request parameter must be optional with a defined default so an old client that never sends it gets the old behavior. A new response field is safer because an old client is expected to skip what it does not recognize, but that only holds if the client tolerates unknown fields. The moment you delete a field, rename it, change its type, or tighten its validation, the change is no longer additive and needs a version or a deprecation path instead.

Run the old client against the new server

Capture the pre-change contract as a baseline, then drive the new server with two identities. The old client sends a payload without the new field and receives a response that still contains the new field; assert that the old fields are unchanged and that the extra field is ignored rather than causing a parse failure. The new client sends the field and asserts it takes effect. Keep both sequences in the same suite so a server that only works for the newest client is caught immediately. A desktop API workspace can hold the two payloads as variables and run them as ordered scenarios against one local mock, with a response override for the new field. Powerduck works this way, keeping the old-and-new comparison local and repeatable instead of relying on a deployed server.

Changes that look additive but break

Change Why it is not safe Test setup Expected result
New enum value Strict enum parser rejects the unknown value Return the new value to the old client Client tolerates or maps it
New required request field Old client cannot send it Call without the field Old default path still works
Tightened validation Inputs once accepted now fail Replay a previously valid request Same success, not a 400
Changed default Omitting the field changes behavior Send no field, compare to baseline Old behavior preserved
New response field Whole-body exact match now fails Assert only the old subset Old fields unchanged

Assert on the subset, not the whole body

Do not compare the entire response to a stored literal, because a correctly added field turns that assertion into a false failure. Instead, validate that the old fields still satisfy the old schema and check the new field on its own, confirming it is optional and never alters the old path. Verify the parser's tolerance for unknown fields explicitly rather than assuming it, and pin the default the server uses when the new request parameter is absent. When a new enum value is involved, the old client should ignore or map it rather than crash, and the server should keep accepting the old values. These checks turn compatibility from a hope into a named set of assertions.

Honest limits

A local mock proves field-level compatibility and the client's tolerance for unknown data, but it does not exercise the real server's serialization, data migration, or gateway layers. It cannot cover every old client in the wild, including third-party integrations and already shipped versions, so the test represents a chosen baseline rather than the full population. Semantic and enum-level breakage also needs real traffic and monitoring to confirm. Calling a change backwards compatible is a statement about the known clients, not a guarantee that no consumer depends on behavior the test did not model. Importing a schema does not deploy a backend, and a passing mock run says nothing about production.

Frequently asked questions

Is adding a response field always safe? It is safe only if the client ignores unknown fields. A client that validates the whole body against a strict schema can fail on the new field.

Why is a new enum value risky? A parser that expects a closed set of values can reject the new one even though nothing was removed. Test that the old client tolerates or maps it.

What proves the old default still works? Send the request without the new parameter and compare the result to the pre-change baseline. The server must preserve the old behavior, not just return 200.

Should I assert the full response equals a saved example? No. Assert the old field subset against the old schema and check the new field separately, so a valid addition does not break the test.

The bottom line

Backwards compatibility is proven by the old client working against the new server, not by the new field functioning in isolation. Run both directions, treat new enum values and shifted defaults as the real risks, and assert the old subset while tolerating unknown fields, then verify real server behavior and shipped clients separately. For more on this approach, see the API testing guide hub.