A status code alone tells a client something went wrong, not what, or why, or what to do about it — and an API that returns a differently-shaped error body from every endpoint forces every integration to write brittle, endpoint-specific error-handling code.
3 min read
404 Not Found tells a client the general category of failure. It doesn't
say which resource was missing, whether the ID was malformed versus
genuinely absent, or what field name to look at in a validation failure.
Real client code — a mobile app deciding what message to show a user, a
partner's integration deciding whether to retry — needs more information
than the status code line alone provides, which is why the error response
body matters just as much as the status code that came with it.
Without a shared convention, different endpoints (or worse, different developers on the same team) tend to shape errors inconsistently:
// Endpoint A
{ "error": "User not found" }
// Endpoint B
{ "message": "Invalid email format", "field": "email" }
// Endpoint C
{ "errors": ["Something went wrong"] }A client integrating against all three has to write three separate parsing paths for what's conceptually the same thing — an error — which is exactly the kind of per-endpoint inconsistency lesson 3's filtering conventions warned against, applied to failures instead of successes.
RFC 7807 ("Problem Details for HTTP APIs") defines a consistent JSON shape any API can adopt:
{
"type": "https://example.com/errors/validation-failed",
"title": "Validation failed",
"status": 422,
"detail": "The 'email' field is not a valid email address.",
"instance": "/users",
"errors": [
{ "field": "email", "message": "must be a valid email address" }
]
}type — a stable identifier (often a URL, though it doesn't need to
resolve to anything) a client can programmatically check to distinguish
error categories, without parsing human-readable text.title — a short, consistent summary of that error type.detail — specifics about this particular occurrence (which
field, which resource).errors above) can carry structured, field-level
detail beyond the base spec, as long as the core shape stays consistent.type matters more than it seemsA client branching on the human-readable title or detail string is
fragile — rewording an error message for clarity becomes a breaking change
for anyone whose code was checking if (title === "..."). A stable type
identifier is designed specifically to be checked programmatically, so the
human-facing text can be improved or translated freely without breaking any
client that correctly relies on type instead of message text.
Lesson 4 mentioned GraphQL returns errors in a dedicated errors array
alongside partial data, rather than a REST-style whole-response status
code. The same consistency principle applies there too — a well-designed
GraphQL API gives every error a consistent shape within that array
(an extensions.code field is the common convention, playing the same
role RFC 7807's type does for REST) rather than ad-hoc, differently-
shaped error objects depending on which resolver happened to throw.
A predictable, documented error shape lets a client write one generic
error-handling path instead of one per endpoint — checking type (or
extensions.code) to decide whether to retry, show a specific message, or
surface a field-level validation error to a form. This is the same
underlying value proposition as lesson 3's filtering conventions and
System Design's status-code discussion: predictability compounds, and it
costs the API provider very little to standardize compared to what it saves
every consumer building against the API.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. Why is a status code alone often insufficient information for handling an API error?
2. Why is RFC 7807's 'type' field more reliable for programmatic error handling than the 'title' or 'detail' text?
3. What's the main problem with different endpoints on the same API inventing their own error response shapes?
API Design