An application programming interface — the surface a piece of software exposes for other code to call, whether that's a library's functions, a web service's endpoints, or a class's public methods — is, from a design perspective, largely a communication problem wearing technical clothing: every name, every parameter, every default value is a small decision about what a caller should be able to expect, and the quality of an API is largely determined by how consistently those small decisions are made across the whole surface. A comprehensive external reference is the Google API Design Guide.
Why consistency matters more than any individual decision's cleverness
A caller learning a new API relies heavily on pattern inference — having learned how one part of the API works, they reasonably expect a similar part to work the same way, unless there's a clear, specific reason for it to differ. An API where similar operations are named or structured inconsistently (one function takes an options object, a very similar function takes several loose positional parameters instead; one endpoint returns an error as a specific status code, a very similar endpoint returns it as a 200 response with an error field buried in the body) forces a caller to memorize each individual exception rather than being able to reasonably infer how an unfamiliar part of the API probably works from the parts they already know. This cost compounds across a large API in a way that isn't obvious from looking at any single inconsistency in isolation. Teams relying on collaboration platforms can compare what activity signals actually mean in https://www.monitask.com/blog/how-does-microsoft-teams-track-activity/.
This connects to the naming guide discussed elsewhere on this shelf, applied at the scale of an entire public interface rather than a single function: a name in a public API is an especially high-stakes promise, because unlike an internal function that only the current team needs to understand, an API's names and behavior are a contract with external callers who can't easily see or ask about the reasoning behind any given inconsistency.
Why breaking changes are so much more expensive in a public API
A change to an internal function's behavior can typically be verified and, if needed, fixed by finding every caller within the same codebase — a bounded, knowable set. A breaking change to a public API's behavior affects every external caller, most of whom the API's maintainers can't directly see, can't necessarily notify effectively, and can't verify have actually updated their code to handle the change. This asymmetry is the core reason API design deserves more deliberate, upfront care than an equivalent internal interface: an internal design mistake is comparatively cheap to fix later; a public API design mistake, once callers depend on it, is often something that has to be lived with for a long time, or very carefully deprecated, discussed elsewhere on this shelf, rather than simply changed.
- Consistency across similar operations matters more than any single decision being individually elegant — callers learn an API by pattern-matching from what they already understand.
- Treat every name and default value in a public API as a promise to external callers who can't see or ask about the reasoning behind it — a higher bar than an equivalent internal design decision.
- A breaking change's true cost is proportional to how many callers depend on the current behavior, most of whom aren't visible to the API's maintainers — which is why public APIs deserve more upfront design care than internal ones.
- Semantic versioning, discussed elsewhere on this shelf, exists specifically to make the distinction between a breaking and non-breaking change legible to callers without requiring them to read every change in detail.
Designing for the caller who hasn't read the documentation
A useful design discipline is considering how an API behaves for a caller who hasn't carefully read its documentation and is instead guessing based on names and common conventions — does a function called getUser throw an error or return a null-like value if the user doesn't exist; does a parameter called timeout expect milliseconds or seconds. Neither answer is universally correct, but an API that answers these kinds of questions consistently with common conventions in its ecosystem, and consistently with itself across its own surface, reduces the number of times a caller has to stop and check documentation for something they could otherwise have reasonably inferred.
Read alongside the versioning and deprecation guides discussed elsewhere on this shelf, API design is really the first of three connected disciplines — designing the surface carefully, communicating changes to it clearly through versioning, and eventually retiring parts of it without breaking the callers who depend on them.