Technical Article

Typed Errors as API Contracts

Errors are contracts just as much as successful responses are. Yet many APIs invest heavily in typing their happy paths while allowing failures to escape as generic 500 status codes, ad-hoc string messages, or inconsistent error shapes that vary wildly across endpoints.

When a client receives a plain string in one scenario, a nested JSON object in another, and an empty response body on a third, frontend and consumer services end up coding against implementation accidents rather than a predictable set of domain outcomes.

Designing Errors Before the HTTP Boundary

A disciplined API architecture models failure long before reaching the HTTP transport layer. Domain and application services represent expected failure modes with explicit, strongly typed results. Infrastructure adapters translate technical faults into these domain models, and the API edge serializes them into a consistent, uniform envelope.

A production-ready error envelope typically provides code, message, and an optional details payload. The linchpin of this structure is semantic stability: the error code must remain invariant so client applications can execute programmatic branching without relying on fragile string matching against human-readable text.

Failures as Public Capabilities

If a business operation can fail due to resource conflicts, entity absence, insufficient permissions, or validation errors, those outcomes belong to the public contract just as much as the 200 OK schema. Documenting and typing failure variants explicitly allows client SDKs and consumers to handle every edge case comprehensively.

This separation also safeguards internal implementation details. It prevents low-level database constraints, ORM exceptions, internal stack traces, and vendor-specific error structures from leaking across system boundaries, where they would otherwise become accidental coupling points.

Testability and Predictability

Typed errors dramatically improve test suites. Rather than asserting merely that an endpoint returned a 4xx or 5xx code, automated tests can assert the exact failure category, the machine-readable error code, and the sanitized details exposed to the client.

A reliable API is not one that never experiences errors; it is one where success and failure share equal semantic clarity, enabling upstream consumers to treat both as expected, well-defined parts of a production protocol.