The Developer Experience Lens: Errors, Docs, Consistency
What This Concept Is
Developer experience (DX) is the quality attribute that measures how easy it is for another engineer to read your contract, write correct code against it, and recover when things go wrong. It is not a vibe. It is assessable against concrete criteria:
- Predictability: similar operations look similar; an engineer who has used one endpoint can guess the next.
- Errors: failures return structured, machine-readable information including a stable code, a human message, and a correlation ID.
- Documentation: every field, status code, and error is documented; examples compile; the spec matches the server.
- Consistency: naming, casing, pagination, timestamps, and units are the same across the whole surface.
- Discoverability: consumers can find the right endpoint without asking you on Slack.
- Graceful evolution: adding a field does not break clients; removing one follows a published process.
Geewax captures this as four properties of a "good" API: operational, expressive, simple, predictable. DX is the operational surface of those four.
Why It Matters Here
DX is the only quality that directly determines integration cost. A service that is fast but inconsistently designed will burn more engineering hours on its consumers than it saves on its producers. Every other decision in this module is a DX decision in disguise:
- verb/status selection (Cluster 2) is predictability
- custom-method discipline (Cluster 3) is consistency
- versioning (Cluster 5) is graceful evolution
DX is also the first failure mode of big API surfaces: 50 endpoints with 50 error shapes is unusable regardless of how well any single endpoint is written.
Concrete Example
Two error responses for the same failure (400 validation error on a missing field).
Bad (inconsistent, unparseable):
{ "ok": false, "msg": "Field 'email' is required." }
Another endpoint on the same API:
{ "status": "ERR", "errors": ["email: required"] }
A third:
HTTP/1.1 500 Internal Server Error
A consumer writing defensive code now needs a separate branch per endpoint.
Good (RFC 7807 "problem details", applied uniformly):
{
"type": "https://api.example.com/errors/validation",
"title": "Request failed validation",
"status": 400,
"code": "VALIDATION_FAILED",
"detail": "One or more fields are invalid.",
"instance": "/orders",
"trace_id": "0af7651916cd43dd8448eb211c80319c",
"errors": [
{ "field": "email", "code": "REQUIRED", "message": "email is required" },
{ "field": "items", "code": "MIN_ITEMS", "message": "at least 1 item required" }
]
}
The same shape appears on every endpoint. Consumers write one error handler. The code is stable across versions. The trace_id routes support tickets straight into observability.
Consistency tax: once you have 5 endpoints using the shape above, endpoint 6 must match. Inconsistency is paid by every consumer, forever.
Common Confusion / Misconception
"We'll fix the errors later - we're shipping the happy path first." Clients have to code for errors from day one. Shipping the happy path with a random error shape locks that shape in. Fixing it later is a breaking change.
"HTTP status codes are enough." They are not. 400 Bad Request can be "missing field", "conflict", "invalid state transition", or "wrong ID format". Clients need a stable code value to decide programmatically what to show the user. The status tells you which bucket; the code tells you which specific problem.
"Good docs make up for inconsistent design." They do not. A developer reading docs has to hold the whole inconsistency in their head. Consistent design means they only have to learn the pattern once.
How To Use It
Run this checklist on any API you design or review:
- Is there exactly one error shape across the whole surface? Point to it.
- Is every error code documented with "what causes this" and "how to recover"?
- Are field names consistent case and style (
snake_casevscamelCase- pick one)? - Are timestamps a single format (
RFC 3339 UTC) everywhere? - Are monetary amounts always represented the same way (minor units integer? decimal string?)?
- Are pagination, filtering, and sorting parameters named the same everywhere (
page_size,page_token,filter,order_by)? - Does every endpoint have at least one request and response example that parses against the schema?
- Is a trace/request ID returned on every response for cross-team debugging?
Each no is a DX debt you pay forever.
Check Yourself
- What is the difference between an HTTP status code and an application error code? Give an example where the status is
400but you need three distinct codes to distinguish causes. - You need to add a new optional field to a response. What is the smallest change to the docs that is still safe?
- Your API returns timestamps as ISO-8601 strings on most endpoints and Unix epoch seconds on one legacy endpoint. Is that a contract bug? How would you fix it without breaking consumers?
Mini Drill or Application
Audit a public API (Stripe, GitHub, Twilio, Slack) or one you use at work against the checklist above. For each question, note:
- pass / fail
- where the evidence lives (docs section, example response, header)
- if fail: what the fix would cost (is it breaking?)
Present the audit as a 1-page memo. Practice this until the audit takes under 20 minutes.