# Error responses follow one structure

- **Pattern:** `ab-000364` (`api-design.contract-quality.error-schema`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000364
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000364)

## Why it matters

When auth errors return `{ message: '...' }`, validation errors return `{ errors: [...] }`, and not-found errors return raw strings, consumers must write a different parser for each error type. This is not just inconvenient — it makes automated error handling (retry logic, error monitoring, user-facing messages) impossible to build generically. iso-25010:2011 compatibility.interoperability and maintainability.modifiability both require a stable, machine-readable error contract. Inconsistent errors are also a debugging liability: different shapes in different production logs make tracing failures significantly harder.

## Severity rationale

Critical because inconsistent error shapes make automated error handling and observability impossible, and every new error type added to the API risks introducing yet another shape.

## Remediation

Define one error structure and use it for every error response across every endpoint. Create a helper to enforce it:

```ts
interface ApiError {
  error: {
    code: string        // machine-readable: "NOT_FOUND", "VALIDATION_ERROR"
    message: string     // human-readable description
    details?: unknown   // field-level detail for validation errors
  }
}

function apiError(code: string, message: string, status: number, details?: unknown) {
  return Response.json({ error: { code, message, details } }, { status })
}

// In every handler:
return apiError('NOT_FOUND', 'User not found', 404)
return apiError('VALIDATION_ERROR', 'Invalid request', 400, zodError.issues)
return apiError('INTERNAL_ERROR', 'Something went wrong', 500)
```

The `code` field is the key — it lets consumers branch on machine-readable values rather than parsing human-readable strings. See the `status-codes` check for HTTP code consistency.

## Detection

- **ID:** `error-schema`
- **Severity:** `critical`
- **What to look for:** Examine error responses across all endpoints. Check whether every error -- validation errors, not-found errors, auth errors, server errors -- follows the same structural shape. Common patterns: `{ error: { code: "NOT_FOUND", message: "User not found" } }` or `{ error: "message" }` or `{ message: "...", statusCode: 404 }`. Look for inconsistency: some endpoints return `{ error: "..." }`, others return `{ message: "..." }`, others return raw strings or HTML error pages. Also check: do error responses include a machine-readable error code (not just the HTTP status)?
- **Pass criteria:** Enumerate all distinct error response shapes across the codebase. 100% of error responses across all endpoints must use the same structure. The structure includes at minimum a machine-readable code and a human-readable message. Validation errors include field-level detail. Report the count of distinct error shapes found even on pass.
- **Fail criteria:** Error response shapes differ across endpoints -- some return `{ error: "..." }`, others return `{ message: "..." }`, others return different structures. Or error responses are raw strings, HTML, or vary in structure by error type. Must not pass when more than 1 distinct error shape exists across the API.
- **Skip (N/A) when:** Never skip -- every API returns errors.
- **Detail on fail:** Describe the inconsistency (e.g., "Auth errors return `{ message: '...' }`, validation errors return `{ errors: [...] }`, not-found returns `{ error: '...' }`, and server errors return raw `Internal Server Error` string. 4 different error shapes across 12 endpoints."). Max 500 chars.
- **Cross-reference:** For HTTP status code usage consistency, see the `status-codes` check in the Developer Ergonomics category below.
- **Remediation:** Define a single error response structure and use it for ALL errors:

  ```ts
  // Consistent error shape:
  interface ApiError {
    error: {
      code: string        // machine-readable: "NOT_FOUND", "VALIDATION_ERROR"
      message: string     // human-readable description
      details?: unknown   // optional field-level detail for validation errors
    }
  }

  // Helper function:
  function apiError(code: string, message: string, status: number, details?: unknown) {
    return Response.json({ error: { code, message, details } }, { status })
  }

  // Usage in every handler:
  return apiError('NOT_FOUND', 'User not found', 404)
  return apiError('VALIDATION_ERROR', 'Invalid request', 400, zodError.issues)
  return apiError('INTERNAL_ERROR', 'Something went wrong', 500)
  ```

---

## External references

- iso-25010 compatibility.interoperability
- iso-25010 maintainability.modifiability

Taxons: code-quality, user-experience

HTML version: https://auditbuffet.com/patterns/ab-000364
