Im my time as a Software Engineer I had the pleasure of interfacing with many external systems. Some of those interfaces are pleasant. UNIX comes to mind, as does Stripe or Google Maps. Some are acceptable. Some are just horrible. They are inconsistent, rely heavily on documentation and go against industry standards. Sometimes this can be explained by incompetence, but most of the time, it is just a lack of care. For many product teams, APIs seem to be an afterthought. This is especially outrageous, if the API is a core component of the product. So in this post, we look at some principles, on how to make good APIs, APIs that encurage others to interface with your software.
When you design an API, you're not just exposing data. You're shaping how people think about your domain. In a well designed API, all operations seem intuitive. A poorly designed one forces developers to carry mental models that have nothing to do with their actual problem. The cost isn't just in integration time. It creates technical debt that accumulates with every workaround, every undocumented quirk and every inconsistent naming scheme.
Don Norman's Principles good of Design
In his book Design of Everyday Things Don Norman, describes principles that underlie good design. These hold true for physical products, for user interfaces and for APIs. Here are some ways in which we can apply his lessons to API design.
Visibility: Make it obvious what's possible. Good APIs use clear, discover-able endpoint structures and consistent patterns. If I can guess the URL for updating a user after I've seen the endpoint for creating one, you've succeeded. If I have to read your source code or wait for support, you've failed.
Feedback: Every action should provide a clear response. HTTP status codes matter. Error messages should be helpful, not generic. A 200 OK that actually means "accepted but will fail later" is a lie that costs days of debugging. Tell me what happened, why it happened, and how to fix it, or at least where to look.
Fail fast, use asynchronous responses only when absolutely necessary. There is nothing worse, than having to query another API endpoint, to discover that my initial request was wrong.
Good Mappings: It has to be obvious, what actions have what consequences. If I have to consult the docs, to figure out what endpoint does what, you have failed. An endpoint /payment should not create an invoice. But a /checkout could. The endpoints I can call have to closely resemble, what I want to do with the API. If I have to deal with a large amount of functionality, that I don't care about, you have failed. If I need to consider a lot of fields, I don't care about, you have failed.
A good conceptual model: Every API needs abstractions. The key is to figure out what the intent of your users is. You wan't a model, that gives the users the right amount of control, not to limiting, not to detailed.
Find the right abstractions
The perfect abstraction is one that hides complexity without lying about what's underneath. Leaky abstractions are the bane of API consumers. If your pagination mechanism requires me to understand your database sharding strategy, your abstraction has failed. If your "simple" file upload API forces me to implement multipart form data handling with custom chunking, you've abstracted at the wrong layer.
The rule is simple: abstract what varies, expose what matters. But behind this simple rule is a mountain of complexity. There is no hard rules to go by. It requires deep understanding of your users application domain and It is more of an art than a science. The best advice I can give is make multiple iterations, talk to your users, and ideally be your own user. The best abstractions don't come from careful analysis, but from feedback and iterations.
The hardest thing in the world: Naming
A name can be so many things. In the best case it captures the essence of what something does. When everything is named perfectly, I don't need documentation, the names already convey what has to be done. Unfortunately it is hard to arrive at that state. And unfortunately, often there is no "right" name. Cultural differences in your users make it so that the same name is interpreted differently by different people.
Ideally you pick names, that your users are familiar with. This often means using names form computer science or your application domain. The more wildly used a term is, the more people will understand it. The more specific a term, the more meaning you can transport with it. But some people might not be familiar with it. In the end, it is a game of trial and error. Talk about naming. It is important. Even if sometimes it seems like the most unimportant thing.
Translation of business concepts
I see this often when German speaking teams create APIs where the business concepts are in German, but the API is in English. They translate the concepts into English, but use different English translations in different places. So in one place it is comprehensive insurance, in another place it is casco. In one place it is general partner, in another it is unlimited liability partner. This often is a result of different people working on different parts of the system. Take care that this doesn't happen. Review you API, before you release it.
Consistency is King
Inconsistency is a tax you impose on every developer who uses your API. It's cognitive overhead, that makes integration feel like archaeology. Use the same authentication mechanism everywhere. Use the same pagination scheme everywhere. Use the same error format everywhere. Reuse business objects where you can.
The inverse is also true: if something looks the same, it should behave the same. If two resources both have a DELETE endpoint, they should both respond with the same status codes and error conditions. If one soft-deletes and another hard-deletes, that's a design failure. Make the difference explicit in the interface.
Documentation is not a substitute for good design
The best APIs need minimal documentation. That doesn't mean you shouldn't document, it means your design should be so clear that documentation is a confirmation, not a revelation. Documentation can't fix a bad design; it can only describe the pain in detail.
Your documentation should tell me things I can't guess: rate limits, deprecation schedules, idempotency guarantees, and the business rules that aren't obvious from field names. If I need to read three pages to create my first resource, your API has already failed. If you need a complex state diagram, to show, in what order I have to execute methods, you have severely failed.
Errors are part of the interface
Errors deserve the same design rigor as success responses. Return structured error codes, not just human-readable messages. Messages are for humans; codes are for code. A good error response tells me if I should retry, fix my request, or contact support.
Don't use HTTP 500 for business logic failures. A payment failing validation is not a server error. It's a 400 Bad Request with a clear explanation. Reserve 500 for when your database is actually on fire. This distinction matters enormously for building reliable integrations.
Build for evolution
The best APIs are designed to change. Use extensible formats. Use enums instead of booleans. There is nothing more confusing than an API with ten boolean options, that might, or might not work in all configurations. Build polymorphism into your designs, this allows you to enable different usages, where it is explicit what fields have to be filled. APIs with hundreds of fields, where some are optional, but required sometimes, are an integration nightmare.
Your API is a conversation with the developers who use it. Listen to them. Monitor how they use it. The endpoint that's called three times in sequence is a clue that you need a bulk operation. The field that's always null is a candidate for deprecation. The error that happens daily is a design flaw.
Final thought
Good API design is not a technical challenge, it's a cultural one. It requires empathy for developers, discipline from product teams, and recognition from leadership that APIs are a core part of a product, not an afterthought. The teams that treat API design as a first-class concern, like Stripe, build ecosystems. The teams that treat it as an afterthought build legacy systems before they've even launched.
The difference between a pleasant API and a horrible one isn't technology. It's care. It's the decision to read your own documentation before publishing. It's the discipline to refactor that inconsistent endpoint even though "it works." It's the empathy to realize that every design choice you make becomes someone's integration headache or someone's nice surprise.
Further reading:
Why API design matters - Michi Henning
How to Design a Good API and Why it Matters - Joshua Bloch, Google
Stripe’s payments APIs: The first 10 years - Michelle Bu, Stripe