The error response is the part of an API that gets designed last and experienced most. A client integrating with your service will spend far more time reading failures than successes, and an error that says only that something went wrong forces them into trial and error against your production system.
Good error design is not complicated. It is mostly deciding what a caller needs in order to do something useful.
What changed in 2026
- Standard error formats gained adoption. Conventions for machine-readable problem details spread, reducing the number of bespoke error shapes clients have to handle.
- Retryability signalling became expected. Explicitly telling clients whether a failure is transient, rather than leaving them to infer it from a status code, grew into a documented practice.
- Trace identifiers in responses became standard. With distributed tracing widely deployed, returning the trace identifier to the caller turned support requests into direct log lookups.
- AI clients raised the bar. As automated agents began consuming APIs, error messages that explain how to correct a request became measurably more valuable than terse codes.
What an error response should contain
| Field |
Purpose |
| HTTP status |
Broad category; keep the semantics conventional |
| Stable error code |
What clients branch on; never reword it |
| Human-readable message |
For developers reading logs, not for end users |
| Field-level details |
Which input was wrong and why, all of them |
| Retryable flag |
Whether trying again could succeed |
| Retry-after hint |
When rate limited or temporarily overloaded |
| Trace identifier |
Ties the caller's report to your logs |
| Documentation link |
Where to read more about this error |
The stable code is the field that matters most and the one most often missing. Clients need something to branch on that will not change, and message strings get reworded by every copy edit. A code like an enumerated identifier survives; a message does not.
The retryable flag is the second most valuable. Status codes carry some information about this, and the mapping is ambiguous enough that clients guess wrong routinely. Telling them explicitly prevents both pointless retries against permanent failures and premature giving up on transient ones — which matters for the amplification problem described in backpressure explained.
Getting the details right
Return all validation failures at once. A client submitting a form with four bad fields should learn about four bad fields, not fix one, resubmit, and discover the next. This is a small implementation change with a large effect on integration experience — and the mechanics are in JSON schema validation guide.
Keep status code semantics conventional. Using a success status with an error body, or returning a client-error status for a server-side problem, breaks every generic client, proxy, and monitoring tool that reasons about status codes. Save the creativity for the body.
Include the trace identifier and log it on your side. When a caller reports a failure and quotes the identifier, you go straight to the exact request rather than searching by timestamp and guessing. This single field probably saves more support time than everything else combined.
Do not leak internals. Stack traces, database error text, and internal service names in a public error response give an attacker a map of your system. Log those internally, return the trace identifier, and keep the two connected on your side only.
Common mistakes
- No stable error code. Clients end up matching on message strings, which you then break.
- One validation error at a time. Turns integration into a guessing loop.
- Success status with an error body. Breaks generic clients and monitoring.
- Leaking stack traces. Reconnaissance material with no benefit to legitimate callers.
- No trace identifier. Every support conversation starts with archaeology.
- Rewording codes for clarity. Reword the message; the code is a contract.
FAQ
Should error messages be localized?
The developer-facing message, no — keep it consistent for logs and debugging. End-user text should be produced by the client from the error code, which is one more reason the code matters.
How specific should error codes be?
Specific enough that a client can respond differently where different responses make sense. A single generic code for all validation failures forces clients to parse the details.
Should I use a standard problem-details format?
Where it fits, yes — it saves clients from learning your bespoke shape. Extending it with your own fields is expected and fine.
What about errors in batch operations?
Return per-item results rather than failing the whole batch on one bad item, and make the partial-success semantics explicit in documentation.
Where to go next
For validation mechanics, read JSON schema validation guide. For overload behavior, backpressure explained, and for evolving contracts, API versioning strategies.