Error responses include codes and messages
Why it matters
Error responses that return only an HTTP status code with no body, or a plain string message with no machine-readable code, force API consumers to parse free-text to determine what went wrong — fragile, locale-sensitive, and impossible to handle programmatically. Returning stack traces or SQL fragments in production error bodies is a CWE-209 violation (information exposure through error messages) that leaks table names, column names, and internal architecture to any caller who triggers a 500. Consistent, structured error responses are required for ISO-25010:2011 compatibility.interoperability.
Severity rationale
High because inconsistent or detail-leaking error responses both break API consumers and expose internal implementation details that assist attackers in crafting targeted exploits.
Remediation
Create a centralized error helper at src/lib/api-errors.ts that produces a fixed response shape and call it from every route handler:
// src/lib/api-errors.ts
export const ERROR_CODES = {
VALIDATION_ERROR: 400,
NOT_FOUND: 404,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
RATE_LIMITED: 429,
INTERNAL_ERROR: 500,
} as const
export function apiError(code: keyof typeof ERROR_CODES, message: string) {
return Response.json(
{ error: { code, message } },
{ status: ERROR_CODES[code] }
)
}
Log the full error server-side (including stack trace) but never include it in the response body. Never return 200 with { success: false } — use the semantically correct HTTP status code so HTTP clients and monitoring tools can act on it.
Detection
- ID:
error-responses-codes-messages - Severity:
high - What to look for: Enumerate all route handlers and for each classify the error response shape. Quote the actual error response structure found in representative routes. Check: (1) Do error responses include a machine-readable error code (not just an HTTP status code)? (2) Do they include a human-readable message? (3) Is the error response shape consistent (same structure for all errors)? (4) Do 4xx errors include enough detail for the API consumer to take corrective action, without leaking internal details? Look for patterns like returning just
{ message: "Internal Server Error" }with no code, or bare HTTP status codes with no body. - Pass criteria: At least 100% of error responses include: an HTTP status code appropriate to the error type (400 for validation, 401 for unauthenticated, 403 for unauthorized, 404 for not found, 429 for rate limited, 500 for server errors), a machine-readable error code string (e.g.,
"VALIDATION_ERROR","NOT_FOUND","RATE_LIMITED"), and a human-readable message. Shape is consistent across routes. - Fail criteria: Error responses return only an HTTP status code with no body; or return only a string message with no error code; or leak internal implementation details (stack traces, SQL query fragments) in error messages; or use inconsistent shapes across routes.
- Skip (N/A) when: The project has no API routes. Signal: no route handlers detected.
- Detail on fail: Example:
POST /api/users returns 500 with no body. Describe the error handling gaps (e.g., "POST /api/users returns 500 with no body on validation error; stack traces returned in production error responses; no machine-readable error codes in any route"). Max 500 chars. - Remediation: Standardize your error responses in a helper at
src/lib/api-errors.ts. Define a fixed error response shape:{ error: { code: "ERROR_CODE", message: "Human-readable message", details: {} } }. Create a centralized error handler or utility function that formats all errors into this shape. Use appropriate HTTP status codes — do not return 200 with{ success: false }in the body (this breaks HTTP semantics and client error handling). Never return stack traces or internal error details in production — log them server-side instead. Common codes to define:VALIDATION_ERROR,NOT_FOUND,UNAUTHORIZED,FORBIDDEN,RATE_LIMITED,INTERNAL_ERROR.
External references
- cwe · CWE-209 — Generation of Error Message Containing Sensitive Information
- iso-25010:2011 · compatibility.interoperability — Interoperability
Taxons
History
- 2026-04-18·v1.0.0·Initial import from saas-api-design·automated