I have noticed that APIs are a bit like abstractions in general. APIs that impress people quickly are very often the ones that cause the most trouble later. I do not mean this as some grand law. Perhaps, I am being slightly unfair, but I have seen enough beautiful APIs turn into maintenance issues to say that.
What I trust more is time. A good API might not be the one that looks the cleanest in a code review. It is the one that still makes sense after other teams start using it, after requirements shift, after the underlying implementation changes, and after somebody does something with it that the original author definitely did not have in mind. Good APIs age slowly. That is probably a much better test than elegance.
Good APIs Age Slowly
The First Version Gets too Much Credit
I think people often like the first version of an API too much because they judge it when everything is still simple. The person who made it knows exactly how it should work, the people using it usually think in the same way, and the system around it has not changed yet. In that situation, almost any API can look good.
Then real life starts. A different team comes along and uses the API from a batch processing. Somebody assumes a field is stable because it has always been there, even though nobody explicitly promised it. Somebody else relies on response ordering because that is how it happened to work in the current implementation. Six months later, when the original team wants to change something inside the system, they realise it is not just an internal change anymore. Other people are already depending on it, even if that was never the plan. That is usually where the bullshit begins because software has a nasty habit of turning observed behavior into dependency whether you meant it or not.
Most API Problems are Boundary Problems
When I think more about API design, I feel the main problem is not beauty, names, or how nice the code example looks. I think the real problem is usually about boundaries. Teams often do not decide early enough what should be part of the public contract and what should stay private inside the system.This seems easy in theory, but in real work people mix these things up all the time. Usually this happens because adding one more field or showing one more piece of state seems safe at the time.
Unfortunately, it rarely stays harmless. Once something is visible, consumers start building around it. From that point on, it does not really matter whether you meant to promise it or not. They saw it, it was useful, and now it is part of their mental model. This is what makes boundary mistakes so irritating. They work just enough for other people to start relying on them. Then later, when you want to change something inside, you realize those internal details have become part of the API.
My gut instinct at this point is to expose as little as possible. I know it’s conservative and had fought some fights on it. Happy to do more because adding later is relatively easy. Removing things, once other people have started relying on them, is where the real pain begins. That is when normal technical choices become difficult talks, migration work, and team politics. Hence, a lot of API stability comes from being careful about the boundary. Teams need to think more carefully about what other people really need to see and what should stay inside.
Convenience Has a Cost
Many APIs are made to feel easy in the current way people use them. At first, that looks good. But many times, easy only means the API has many hidden assumptions. It may assume calls happen in a certain order. It may assume the same type of user. It may assume the same timing. It may assume the caller thinks like the person who made it. At the beginning, this can look like good design.
Then a new use case shows up and the API suddenly feels stiff and awkward. I am not saying every API needs to spell out every little thing because that would be dumb too. Nevertheless, when an API tries too hard to be helpful and guesses too much, it is usually moving the complexity. Later that turns into debugging pain, migration pain, or just teams wasting time trying to figure out what the hell is going on.
That is why boring APIs often last longer. They may not feel as nice at first but at least they are honest. They show the boundaries more clearly. They make the state more visible. They leave less magic hidden in the background. And less magic usually means less bullshit later when the system changes.
Your API Is Not Your Frontend
Another thing that seems harmless until it is not is designing APIs around the current frontend shape. I understand why people do it. A page needs some payload in a certain structure, the backend returns exactly that structure, and everybody moves on. It feels practical. Sometimes it probably is practical.
But a screen is not a domain model, and tying your API too closely to a temporary UI decision is one of those things that works right up until the product changes. Then your API starts feeling oddly specific, like it belongs to a page that no longer exists. Now either the backend carries old nonsense forever or you begin a long, annoying cleanup that could have been avoided by modelling something slightly more durable from the beginning.
I do not think APIs should ignore use cases, because that turns into architecture astronaut nonsense very quickly. But they should usually lean toward the stable concepts of the system rather than the exact shape of today’s page. Otherwise the API ages at the speed of product iteration, which is far too fast for something other teams may build on.
Versioning Does Not Save You
I sometimes get the feeling that people talk about versioning as if it absolves bad API design. It does not. Yes, of course versioning matters. But if an API keeps changing because it was too coupled to implementation details, or too eager to be clever, or too specific to one use case, you will still pay for that. People still migrate. They still retest. They still carry compatibility code and operational overhead.
Stable APIs create trust. I know that sounds fluffy. Yet, it is actually a concrete engineering benefit. People build on top of them without defensiveness. They stop expecting surprise breakage. Coordination cost drops. The API becomes infrastructure rather than drama. That, to me, is a much better sign of quality than elegance.
Famous Last Words
I find it interesting that a good API should not try to be too balanced in every direction. It can be a bit loose in what it accepts, that is usually fine. But it should be much more careful in what it sends back. Ignoring an extra field is usually not a big deal. Accepting a bit more than you need is often fine too. But once you start returning extra data or exposing extra behaviour just because it seems harmless, that is where things go sideways. People see it, use it, and then rely on it. So maybe a good API is not just small. Maybe it is just careful about what it lets out.
