Error Models
A stable error contract: machine-readable codes, a taxonomy clients can branch on, field-level validation feedback, explicit retryability, and honest partial-failure semantics.
Every lesson below names the consumers, the design question and the guarantee before recommending anything. Recommendations come with what they cost, when not to use them, and how they evolve.
A failing response is still a response, and clients write code against it. A stable error model — machine-readable code, human message, request id, structured details — is a contract clause, not a courtesy.
Validation, authentication, authorization, not-found, conflict, rate-limit, dependency, internal: eight categories with different owners, different fixes and different retry rules. Collapse them and every client guesses; distinguish them and clients can be correct.
Reject invalid input at the boundary, and say exactly which field failed which rule — all of them, in one pass. A validation error is a collaboration with the caller; "bad request" is a verdict nobody can act on.
Every error answers a question the client is definitely asking: do I try again? A contract that states retryability explicitly — status semantics, Retry-After, a retryable flag — replaces a thousand guessed retry loops with one correct one.
A batch request where some items succeed and some fail has no honest single status code. The contract must choose — atomic, best-effort with a per-item report, or a mix — and say so before the first consumer assumes the wrong one.