I want to talk about something that costs development teams thousands of hours every year and almost never shows up in sprint planning, code reviews, or performance reviews. API design. Not API development. Not API security. Not API documentation. The design itself. The decisions you make before you open your code editor. I spent three days debugging a payment system for a client last year. Every single request came back 200 OK. Every order looked successful on the dashboard. Money was not moving. After three days I found it — the API was designed to return 200 for every response regardless of what actually happened. The developer who built it thought they were being helpful by normalizing the response format. What they actually did was make every failure invisible. That is not a code bug. That is a design decision with a real cost. The Invisible Cost of Inside-Out Design Most APIs I have encountered were designed from the database outward. The developer builds a schema, creates the tables, and then exposes that structure directly as endpoints. It feels efficient because you are not doing any extra transformation work. But what you are doing is forcing every API consumer to learn your internal data model. This matters because your internal model will change. Tables get split. Fields get renamed. Relationships get restructured. Every one of those changes becomes a potential breaking change for every consumer of your API. The APIs that developers genuinely enjoy using do the opposite. They start with what the developer needs to accomplish and then figure out how to provide that cleanly without leaking internal details. Stripe is the obvious example here. Their API is not praised because of the technology stack behind it. It is praised because every single endpoint was designed around what a developer is trying to do — not around how Stripe organizes its internal systems. That design decision is a large part of why they dominate their market. Five Things Every API Should Get Right Use Status Codes Like They Mean Something HTTP status codes exist as a shared communication protocol between your API and every client that will ever call it. When you return 200 for an error you are breaking that protocol and forcing every consumer to implement their own error detection layer inside the response body. That layer will have bugs. Those bugs will be difficult to trace because at the HTTP level everything looks fine. Meanwhile every HTTP library, every monitoring tool, and every developer on earth already knows how to handle a 400, a 404, a 422, and a 429 correctly if you use them. This is not advanced knowledge. Production APIs get it wrong constantly. Write Error Messages for the Person Who Has to Fix Them There are two kinds of error messages. Ones that describe what happened and ones that tell you what to do about it. validation_failed is the first kind. Expected a string for email, received null is the second kind. The second kind cuts debugging time significantly. When the error tells me exactly which field failed, what it received, and what it expected, I can fix it on the first try instead of the fifth. Good error responses are not documentation. They are part of the API contract. Version From Day One Every API will change. This is not pessimism — it is a guarantee. Business requirements evolve. Features get added. Fields get renamed. If there is no versioning strategy built in from the start, every change is a potential breaking change for every consumer in production. Adding versioning after the fact means running two parallel versions while migrating consumers. Building it in from the start costs almost nothing — a v1 in the URL path, a consistent pattern and gives you the freedom to evolve without forcing everyone downstream to react on your timeline. Make Write Operations Idempotent Networks fail in both directions. A request can time out after the server processed it but before the response was received. The client retries. Now you have a duplicate. For any endpoint that creates a resource or processes a transaction this is not an edge case. It is a predictable failure mode that happens in any real production environment. Idempotency keys solve this entirely. The client sends a unique key with each request. The server checks whether it has already processed a request with that key and returns the same result if it has without repeating the operation. Stripe and Shopify both implement this. Their developer experience is widely considered the industry benchmark that is not unrelated. Hide Your Implementation Details An API that mirrors your database schema is an API that will break whenever your schema changes. Your persistence layer is an internal concern. The developer on the other side of your endpoint should never need to understand how your data is stored to use what you are offering. This also prevents a common coupling problem: when the API shape and the database shape are the same thing, any refactoring of one requires changing the other. Separating them gives you room to evolve each independently. API Design Is a Skill Not a Side Effect API design sits at the intersection of system architecture, developer experience, and communication. It does not improve automatically as you write more code. It improves when you deliberately think about the person on the other side of your endpoint before you write it. I am a web developer. I think about this every day. Every API I ship is something another developer or a future version of me will have to integrate with. The time I spend on design before implementation is the most leveraged time in any project I work on. If you are building APIs or planning to build one, start with a single question. What does the developer using this actually need to accomplish, and how do I get them there without making them understand how my system works internally.
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)