API Design

The Hidden Cost of Poor API Design: Why Developers Must Think Beyond Functionality

An API becomes expensive when every consumer has to rediscover its rules. Design the contract around predictable outcomes and safe change.

3 min read

An endpoint can return the correct data and still be difficult to use. If clients must guess what an absent field means, parse human error messages, or retry a payment without knowing whether it succeeded, the integration cost moves outside the service.

Good API design makes the uncertain parts explicit. The contract should help another team build a correct client without needing access to your implementation or a private explanation of every edge case.

Design the journey before the endpoints

Start with a real consumer task, such as creating an order and finding out whether payment completed. List the states the consumer must understand, including rejected input, insufficient permission, pending work, and a response lost after the server committed a change. Those states are part of the interface, even when the first successful demo does not show them.

For asynchronous work, give the caller a durable operation identifier and a way to inspect progress. For collections, define ordering and pagination together. A cursor without a stable ordering rule can produce confusing duplicates or gaps as records change. Decide what consistency the client can expect and describe it without promising more than the storage system provides.

Give errors a contract of their own

Clients should not branch on prose such as 'Something went wrong'. Provide stable machine-readable information alongside a useful human explanation. Separate invalid input from authentication failures, authorization failures, conflicts, and temporary service problems. Keep sensitive implementation details in internal logs, associated with a request identifier.

RFC 9457 defines Problem Details for HTTP APIs, including fields such as type, title, status, detail, and instance. It offers a shared structure rather than requiring every service to invent an incompatible error envelope. Your application still needs to define the meaning of its problem types and avoid leaking secrets through descriptive fields.

{
  "type": "https://example.com/problems/order-conflict",
  "title": "Order cannot be changed",
  "status": 409,
  "detail": "This order has already entered fulfillment."
}

Treat examples and schemas as engineering assets

A schema is most useful when it agrees with deployed behavior. OpenAPI describes paths, operations, parameters, request bodies, and responses in a machine-readable document. Use that contract to support review, client generation where appropriate, and automated compatibility checks. A generated client cannot compensate for an inaccurate schema.

Include examples for an empty collection, a validation error, and a partial or pending result, not just a large successful payload. State whether timestamps include an offset and whether an amount is an integer in a defined currency unit. Small ambiguities become repeated bugs when several clients independently make the same plausible assumption.

Plan how consumers will survive change

Before changing a field, identify which clients depend on it and how quickly they can update. Adding a new enum value, tightening validation, or changing a default can break consumers even when the response remains valid JSON. Versioning is a communication mechanism, not permission to ignore migration cost.

Review a proposed API using a small client written from the documentation alone. Ask that client to handle a timeout, retry a safe operation, and recover from invalid input. The friction revealed by this exercise is valuable evidence. An API earns trust when ordinary behavior is predictable and unusual behavior is documented well enough that the consumer can make a deliberate decision.