The contract is the product
Consumers do not experience your architecture. They experience your endpoints, your error messages and how often those change without warning. Everything behind that surface is an implementation detail you are free to rewrite — provided the surface holds.
Generate clients from a specification rather than writing them by hand. A drifting SDK is worse than no SDK, because it teaches people not to trust the documentation.
Design errors as carefully as successes
Most integration time is spent on failure handling. A consistent error envelope with a stable machine-readable code, a human-readable message and a retryable flag saves every consumer from writing the same defensive parsing.
- A stable error code that never changes meaning
- An explicit retryable boolean so clients need not guess
- Rate-limit headers that state when to try again
- A correlation identifier that appears in your logs too
Idempotency is not optional
Any endpoint that causes a side effect needs an idempotency key. Networks retry, queues redeliver and users double-click. Without a key, every one of those becomes a duplicate record someone reconciles later by hand.
Version deliberately, deprecate publicly
Additive changes should never require a version bump. When a breaking change is genuinely necessary, publish the window, instrument who is still on the old version, and contact them directly. A deprecation nobody was told about is an outage with extra steps.