A useful error response combines an appropriate HTTP status with a stable machine-readable code, a safe human message, and only the structured details needed for recovery. It distinguishes caller correction from conflict, missing authority, throttling, and server failure. Internal exceptions belong in protected diagnostics linked by a request identifier, not in the public body.
Who this is for: Backend and client developers defining how applications communicate validation, conflict, access, rate, and server failures.
- Design errors as versioned client contracts with documented recovery behavior.
- Separate public problem details from private exception diagnostics and sensitive input.
- Test whether clients handle each failure class instead of collapsing everything into a generic message.
Classify the outcome accurately
Status codes let general HTTP software understand the broad outcome. Invalid syntax or fields differ from an unauthenticated request, forbidden action, missing resource, current-state conflict, throttling, or unexpected server failure. Do not return success for a failed command merely because the server produced a JSON body.
Some boundaries require policy judgment. A private resource may return not found instead of forbidden to avoid confirming its existence. A domain precondition may be represented as validation or conflict depending on whether current server state is central. Choose one rule, document it, and apply it consistently across equivalent operations.
Give clients stable structure
A machine code such as inventory_unavailable is safer to branch on than English prose. Include a concise title or message for people, a request identifier for support, and structured field issues when the client can correct input. Keep field paths and allowed values aligned with the request contract.
Do not promise that every error has the same optional details. Define a small base shape and type-specific extensions. Clients should tolerate unknown fields and codes by using the broad status as fallback. Human messages may change or be localized; they should not become program control flow or be shown without product review.
Protect internal and private information
Never return stack traces, SQL fragments, file paths, secrets, or raw upstream responses to an untrusted client. They can expose implementation and user data. Map known domain failures deliberately, then convert unexpected exceptions into a generic server response while retaining protected diagnostic context under the same request identifier.
Validation detail can also leak. A sign-in or password recovery endpoint should not reveal whether an account exists when that enables enumeration. A cross-tenant identifier should not disclose another organization's resource. Redact logs as well as responses, because a safe public body does not compensate for credentials copied into centralized diagnostics.
Make recovery and observation explicit
Tell clients what they can reasonably do. A field error invites correction, a version conflict invites refresh and merge, and throttling may include a retry hint. Do not encourage automatic retry for permanent failures. For transient server failures, clients should use bounded backoff and idempotency where repeating a write could have an effect.
Track error code rates, affected operations, latency, and dependency health. Alert on unexpected server failures and major shifts rather than every individual client mistake. Contract tests should assert status and code for representative failures. Client tests should verify accessible, actionable presentation and preserve entered data where correction is possible.
Return a version conflict
Two editors modify the same project, and the second save uses an outdated version number.
- Compare the submitted version with current server state in the protected update rather than trusting an earlier read.
- When they differ, return a conflict status and stable stale_version code without overwriting the newer content.
- Include the submitted field path and safe current version identifier, but do not expose another editor's private draft notes.
- Have the client preserve local work, fetch the current document, and offer comparison or deliberate overwrite according to policy.
- Log the request identifier and versions, then test both stale and current saves through the client workflow.
Error contract table
Create one row for every expected failure that clients must handle distinctly.
- Trigger and class: condition, status, stable code, retryability, and idempotency requirement.
- Public details: safe message, field paths, allowed remediation, request identifier, and localization owner.
- Protected details: exception cause, dependency, safe context, redaction, retention, and access control.
- Client behavior: correction, sign-in, refresh, wait, support path, preserved state, and fallback.
- Verification: contract test, client test, privacy case, metric, alert threshold, and runbook link.
Common mistakes
- Returning raw exception messages and stack traces because they seem useful during development.
- Using one 400 response for invalid fields, stale state, missing credentials, and internal failures.
- Making client code compare localized human prose instead of a stable status and error code.
Try one
Design errors for a file upload that can fail because of type, size, missing permission, rate limit, or storage outage.
A strong answer maps each condition to a distinct broad status and stable code, supplies safe correction details for type and size, avoids revealing private storage paths, and includes a retry hint only for throttling or a genuinely transient outage. It links unexpected failures to protected diagnostics with a request identifier and tests that the client preserves context and offers the right next action.
Sources
- MDN HTTP response status codesMDN's reference for standard HTTP response status codes.
- MDN JavaScript error referenceMDN's reference for JavaScript error objects, causes, and stack information.
- GitHub REST API documentationGitHub's primary documentation for REST API requests, resources, and versioning.