In software development, a “Design Smell” is a signal that something isn’t quite right. Think of it like your car sounding a little bit different or your Spidey Sense tingling. In the best of times, you can immediately point at the problem and know how to fix it. But the rest of the time, it becomes an abstract feeling of unsettledness that rattles around in your head, feels awkward, and distracts from the rest of the project.
Not surprisingly API Designs can have similar smells.
After designing and building APIs for nearly 20 years, I’ve found that even if you can’t point at the specific wrong thing, the wrongness itself leaves symptoms and a clear trail. Across a hundred+ APIs across dozens of industries, I’ve collected a few common symptoms that point to problems.
Disclaimer: If you think one of the examples described here resembles your API, it’s entirely possible it does. If you detected the problem on your own, congrats! That’s awesome! If this post makes the problem click for you, congrats! That’s awesome! If you have done everything right and your API is perfect, obviously I wasn’t talking about your API.
The single most common sign are inconsistent names. In the easiest case, you’ll see this in userId vs user_id or ResourceName vs resourceName. On the surface, this looks like a simple miscommunication within a large team but it actually points at something deeper.
The most concrete example I’ve ever seen of this was at a bank and how they named interest rate. One team named it interest_rate while another called it intRate while a third called it int_rate and yet another called it iRate. Unfortunately, the communication breakdown didn’t end at naming but went deeper into the data structure itself. One team represented the interest rate as a decimal (0.05), others as a simple percent (5.0), and others as basis points (500).
Inconsistent names often point at a fundamental difference in how we’re each looking at the situation. When you see a misnamed field, parameter, or resource, stop and dig in to make sure everyone has the same understanding. It will save you frustration later.
CRUD over HTTP
Odds are the first iteration of your API is simply mapping the HTTP verbs of GET, PUT, POST, and DELETE to each of your resources. If that’s where your design begins and ends, it may be clear and simple but you’ve built a glorified database. A good API shouldn’t just wrap database fields but should include business logic, validation, and potentially even workflows.
A simple way to detect this one is compare your API’s parameters, fields in the payloads, and the fields in the database. If they match 1:1 without any modification or additional logic, you have the beginnings of an API but haven’t provided any real functionality on top of the database. Before you continue, go back to your API design and make sure you understand the activity you’re modeling and ensure you’ve covered it properly.
There’s always that customer. Their workflow almost matches your API but there’s one thing that is just a little different and doesn’t quite fit. Sure, they could rework their process but “if you could just..” and it’s so tempting. It means cash – probably a lot of cash – for one little feature. The dirty secret is that almost every big customer will want and even demand the same. And in that way, madness lies every single time. Every one-off capability, feature flag, or field creates at least one more execution path to test and validate and the last thing you need is more complexity.
While it’s easy to notice and stop this at the business-level, maybe some enterprising VP or Product Manger slipped it into your spec while you weren’t looking. Luckily, it’s just as easy detecting it in the API design stage. Are there fields or parameters that will almost always be null? Are there flags to transform how data is returned? Are there use cases or resources that don’t quite fit with anything else? Is there a use case, parameter, or field named after a customer? If you run into any of these, stop and find out where the requirements came from, how you could address them, and how you will address them.
Of all the API Design Smells I’ve seen, this was a new one for me. We’re used to seeing /v1 or similar in our API urls and while it’s not a perfect pattern, it’s well-established and predictable. Working on an API design recently, I noted the team included /v1.1 or /v2.3 in their URLs. Digging into it more, they treated each resource as its own “API” with its own capabilities, versioning, and infrastructure. Since most of the underlying resources were stored as documents instead of relational structures, they found it easy to “upgrade” resources to later versions. Further, they implemented a pattern where interacting with v2.3 of a resource would fall back to v2.2, 2.1, or even 2.0 based on whichever was available.
In short, this was a mess. When people think of an “API” they don’t think of individual resources as individual APIs. They conceptually put all the existing resources into a single API with a single version. By pushing per-resource versioning, they gave lots of flexibility but with the drawbacks of breaking the users’ understanding of APIs, not clearly mapping compatibility rules, and not having useful SDKs to ease and speed adoption. Trying to use v1.1 of this resource with v2.3 of another within the same workflow just didn’t make sense.
The single most important thing about APIs is that they offered us a shared understanding that moves with us from API to API and sometimes even from vendor to vendor. When we want to break this shared understanding, we need realize that the ramifications can extend far beyond this one thing and may slow or even stop the adoption of our API.
Whenever you find yourself in a position where things don’t feel right, trust your instincts. Stop, look around, and find out what the oddity is and what you can do about it. Remember that it will never be cheaper or easier to fix issues than before there’s any code, systems built, or customers dependent on you.