An interface written for one internal caller tends to leak the database schema, return whatever was convenient, and change without notice. That is entirely reasonable until it has four consumers, at which point every one of those properties becomes a liability.

The transition happens quietly, and usually nobody marks the moment.

Design against the use case, not the table

An endpoint that mirrors the storage layer couples every consumer to your schema, so a routine normalisation becomes a breaking change for people you have never met.

Model the response around what the caller is trying to accomplish. It is more work once and it decouples permanently.

Version before you need to

Adding versioning to an API with live consumers is painful. Establishing it while there is one caller costs almost nothing. The same applies to a deprecation policy — a stated notice period is only credible if it existed before the first removal.

Errors are part of the contract

Consumers build logic on failure responses, so an error format is as much a contract as the success shape. Consistent codes, a machine-readable reason, and a clear distinction between "your request was wrong" and "we failed" determine whether integrations retry correctly or hammer you during an outage.

Contract tests over documentation

Prose describing an API drifts. A contract test fails in CI the moment behaviour diverges from what consumers expect, which is the only mechanism that reliably prevents accidental breakage.

Generate reference documentation from the schema so it cannot disagree with the implementation.

Know who is calling

Per-consumer identification and usage metrics turn "can we remove this field?" from a guess into a query. Without it, every deprecation is a gamble and most teams simply stop deprecating.

The cost of these habits is small at the start and large later, which is exactly the shape of decision that tends to get deferred.

Written by the Global IT Solutions engineering team. Have a project this touches on?

Start a conversation