A bare `500 Internal Server Error` with no body tells the engineer on the other end of the integration exactly nothing — not what failed, not whether retrying will help, not whether it's their mistake or yours. For any API past its first few weeks of production traffic, error responses are not a rare edge case: every malformed request, expired token, duplicate submission, and rate-limit hit produces one, and in aggregate a mature API's error paths get exercised as often as its happy path does. A consumer who cannot tell those failures apart programmatically is forced to either string-match an English sentence to decide what to do next, or give up and show the user a generic "something went wrong."
Treat the error contract with the same design discipline as the success contract. A stable, machine-readable code the client can switch on, a human-readable message meant for a developer reading logs rather than an end user, and enough structure to act on — which field was wrong, what was expected instead — are not nice-to-haves layered on afterward. An API that documents its 200 response exhaustively and describes its 4xx and 5xx responses as "an error object, shape varies by endpoint" has only designed half of its actual contract, and it is the half consumers hit constantly.
The cost of skipping this is concrete and compounds over time. Support gets tickets that are really integration questions. On-call gets paged for a client's malformed request because nobody can tell a client mistake from a server bug from a downstream dependency failure without reading logs by hand. Every SDK written against the API — and a popular API accumulates several, across languages, written by people who never talked to each other — ends up inventing its own inconsistent guess at what a given error actually means.
Analogy🏏Cricket
🏏 Think of it like cricket: When a bowler appeals for lbw against a batter like Rohit Sharma and the on-field umpire says not out, DRS doesn't just flash a green or red light and leave the fielding side to guess — it walks the captain and every viewer through exactly why: where the ball pitched, whether the point of impact was in line with the stumps, whether it would have gone on to hit them, and by how much margin, plus UltraEdge showing whether bat made contact first. A captain deciding whether to burn a review on a similar delivery next over isn't told just "wrong decision" — they're told which specific part of the decision was in question and by how much, which is exactly the information that makes the next decision faster and better informed instead of another guess. Compare that to how an lbw shout was communicated before DRS existed: the umpire's raised finger, or the lack of it, was the entire message, and a batter walking off had no way to know whether the call was plainly wrong or genuinely close, nothing to learn from, nothing to appeal against except the umpire's word taken on faith. Just as DRS turns "wrong decision" into a specific, structured breakdown a captain can actually use to decide what to do next, a good API error turns "request failed" into a specific, structured breakdown a client can actually use to decide what to do next. Just as a captain reviewing without knowing which part of a call was in question would be reviewing blind, a client catching a bare 500 with no error code is debugging blind, unable to tell a validation mistake from a downstream outage without guessing. The insight is that a verdict without a reason attached is not useful information — it is just a verdict — and both a review system and an API earn trust by explaining themselves every single time, not just when it's convenient.
🏏 Showing the Cricket analogy — a Cricket version isn’t available for this concept yet.