Nobody reads an error message on a good day.
By the time a developer sees one, something has already failed. The request bounced. The integration they promised their team is broken, and they are guessing at why. That moment is a designed decision that potentially shows up at one of the worst times in the developer’s experience.
Most teams design and review the happy path carefully. The error path rarely gets the same treatment. It’s often whatever the system already returns by default, inherited from a framework or a library, never revisited once the request works.
The research has been saying this for years
Brad Myers and Jeffrey Stylos, summarizing more than a decade of API usability research at Carnegie Mellon, are blunt about it: “A surprising number of APIs supply unhelpful error information or even none at all when something goes wrong” [1]. They connect that directly to lower usability and to possible correctness and security problems.
Their larger point is that Nielsen’s usability heuristics (the same ones we use to evaluate visual interfaces) apply to APIs. “Help users recognize, diagnose, and recover from errors” is one of the ten. So is visibility of system status; their example is that writing to a closed file should result in a helpful error message [1]. And this is not only theoretical. A study of SAP’s early enterprise service-oriented architecture APIs, described by Myers and Stylos, found “poor error messages when API users made mistakes” alongside overlong names and unclear dependencies [1].
Martin Robillard surveyed and interviewed professional developers at Microsoft about what made APIs hard to learn. Inadequate learning resources topped the list of obstacles, ahead of problems with the APIs themselves, and one of the specific gaps developers named was documentation that omitted “information about all exceptions raised” [2].
Let that sink in: the failure modes of the product are the part the designers of that system chose not to write down.
The worst errors are technically correct
Most bad error messages are not wrong, they’re accurate reports of a failure that name nothing anyone can act on. The request was invalid, the operation could not be completed, etc. Every word true but no path forward.
An error that reports a failure without naming the rule that got broken is technically correct and practically useless, and it ruins a critical moment in your API design where you have a developer’s complete attention plus the exact context of what they were attempting. Robillard points to Bloch’s advice that API designers should “obey the principle of least astonishment,” because surprised users introduce bugs [2]. An error is where astonishment either gets resolved or compounds. Which one depends entirely on whether the message names the rule.
A status code is not an error message
Microsoft’s REST API guidance is worth reading here because it gets concrete. On a 400, it treats the response body as the place the actual explanation belongs, or at minimum a link to one [3]. Note the modality in that recommendation. The body is available to you, but nothing forces you to put anything in it. A bare 400 says nothing about which field was wrong or what a correct one would have looked like.
The same guidance shows how much information precise status codes can carry on their own. A PUT that fails because of the resource’s current state is a 409 Conflict, a patch document in an unsupported format is a 415, and a search that finds no matches is a 204 with an empty body (a success) [3]. Precision at this level is free information for the consumer (computer or human). It’s also the floor, yet most APIs stop there.
There’s another small detail in Microsoft’s doc that says a lot: if a client requests 1,000 items and your maximum is 25, the service can return 25 items or return an error “depending on the API documentation” [3]. Either behavior is defensible, but choosing one silently is not. The choice is part of your API contract, someone has to make it, and it shapes what every integrating developer experiences. That is a design and product decision, whether or not a product person was in the room when it got made.
What bad error design costs
Learning time is not spread evenly across an integration. It bunches up in the stuck periods, and stuck periods sit almost entirely downstream of an error somebody couldn’t interpret. Every unhelpful message lengthens debugging time. The stuck period is also when a developer forms their opinion of what your platform is worth, which makes it an expensive place to leave them alone.
The same authors list minimizing support costs among API designers’ goals [1]. Every error a developer can’t resolve alone becomes a support ticket or an abandoned integration.
The security stakes are documented too. Myers and Stylos cite a study by Fahl and colleagues that found 8 percent of around 13,000 popular Android apps misused SSL or TLS APIs and were vulnerable to man-in-the-middle attacks. A follow-up study of iOS apps found 9.7 percent vulnerable [1]. They also report that some iOS frameworks shipped with SSL certificate validation off by default, and API users left it that way in deployed applications [1]. A dangerous default that fails silently is the extreme case of a dishonest error surface: no error at all.
Test the error path like an interface
Here’s the part I find most interesting as a designer: nobody has settled what the “best” error format looks like. Myers and Stylos note that many approaches to reporting errors exist “with little empirical evidence (but lots of opinions) about which is more usable” [1]. The honest answer is the same as for any interface question: watch real developers hit real errors and see whether they recover without help. I’ve planned and run usability research on developer-facing products, and the error moments are reliably where sessions turn.
Here are some ideas to get you started:
- Inventory the errors your API can return and read each one the way an outside developer would, with zero internal context.
- Literally read error messages out loud. Do they even make grammatical sense (you’d be surprised)? Review error copy with the same care you give UI copy.
- Make every response body answer two questions: what went wrong, and what do I do next.
- Give the error catalog an owner, because if nobody owns it, the default wins, and the default is a bare status code.
When a developer is stuck at midnight with a failing request, your error message is the whole product. Write it like one.
References
[1] Myers, B. A., & Stylos, J. (2016). “Improving API Usability.” Communications of the ACM, 59(6), 62-69.
[2] Robillard, M. P. (2009). “What Makes APIs Hard to Learn? Answers from Developers.” IEEE Software, 26(6), 27-34.
[3] Microsoft. “Best practices for RESTful web API design.” Azure Architecture Center. https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design