Designing APIs your future self won't resent
An API is a promise to everyone who builds on it. Once another team, another system, or another company depends on the shape of your interface, you can no longer change it freely. That makes API design one of the highest-leverage decisions in software — and one of the easiest to get quietly wrong in ways that only hurt later. Here are the principles we hold to when designing one.
Predictability beats cleverness
The best APIs are boring. They behave the way you would guess, name things consistently, and contain no surprises. If listing one kind of resource works a certain way, listing every other kind should work the same way. If one endpoint paginates results in a particular style, they all should. Every inconsistency is something the people using your API have to learn, remember, and work around. Consistency is a feature, and it is worth more than any individual clever touch.
Make the right thing easy and the wrong thing hard
A well-designed interface guides people toward correct use. If a value must never be negative, the type system or validation should make a negative value impossible rather than merely discouraged. If two parameters only make sense together, the design should make it awkward to supply one without the other. Every misuse that the design itself prevents is a class of bug that simply cannot happen, and a support conversation that never needs to be had.
The cost of an API is paid by everyone who uses it, every time they use it. Spend your own effort to lower theirs.
Errors are part of the interface
How an API behaves when things go wrong is just as much a part of its design as how it behaves when things go right, and it is far more often neglected. When a request fails, the response should make clear what went wrong, whether retrying might help, and what the caller could do differently. A consistent, well-structured error format — with a stable code the caller can branch on and a human-readable message for the developer — turns failures from mysteries into something a caller can handle gracefully. Returning a generic failure with no detail just moves the debugging onto everyone who depends on you.
Design for change from the start
No API is right forever; the business it serves will change. The question is whether you can evolve it without breaking everyone who depends on it. The most reliable habit is to add rather than to alter: introducing new optional fields and new endpoints is safe, while removing fields, renaming them, or changing their meaning is not. We design responses so that clients ignore fields they do not recognise, which lets us add information without breaking anyone. And when a breaking change really is necessary, an explicit versioning strategy lets old and new coexist while consumers migrate at their own pace.
Be careful what you promise
Every detail a caller can observe risks becoming something they depend on, whether you intended it or not. The exact ordering of an unsorted list, the precise wording of a message, an internal identifier that happens to leak into a response — people will build on all of it if you let them, and then you cannot change it. We try to expose only what we mean to support, and to be deliberate about what is part of the contract and what is merely an implementation detail that callers should not rely on.
Document with examples
Reference documentation that lists every field is necessary but rarely sufficient. What actually helps someone get started is a concrete example: a real request and the real response it produces. We make sure our API documentation shows worked examples for the common cases, including what errors look like, because that is what people copy, adapt, and learn from. A good example is worth pages of prose.
The interface outlives the implementation
You can rewrite the code behind an API as many times as you like; the people depending on it will never know, as long as the promise holds. But the interface itself, once adopted, is remarkably hard to change. That asymmetry is the whole reason to slow down and get the design right at the start. The implementation is yours to change forever. The interface, in a real sense, stops being yours the moment someone else starts to rely on it — so design it for them, and for the future version of you who has to keep the promise.
Written by the Arcwell engineering team. If you're wrestling with something similar, we're happy to compare notes.