Skip to main content

Consistent response envelope format

ab-002166 · saas-api-design.api-consistency.consistent-response-format
Severity: mediumactive

Why it matters

Inconsistent response envelopes — some routes returning { data: ... }, others returning raw objects, others using { success: true, result: ... } — force every API consumer to write bespoke error-handling logic per endpoint. This violates ISO-25010:2011 compatibility.interoperability and means a new integration can silently swallow errors that use the wrong shape. In practice, it produces bugs where a frontend assumes data is present but the route returned the payload at root level.

Severity rationale

Medium because envelope inconsistency causes integration bugs and broken error handling but does not directly enable data exfiltration or service disruption.

Remediation

Define a shared response helper at src/lib/api-response.ts and replace all direct Response.json() calls with it:

// src/lib/api-response.ts
export const apiSuccess = <T>(data: T, status = 200) =>
  Response.json({ data }, { status })

export const apiError = (code: string, message: string, status: number) =>
  Response.json({ error: { code, message } }, { status })

Success responses always carry { data: <payload> }; error responses always carry { error: { code, message } }. Import and use these in every route handler. This gives clients a single shape to handle and gives you a single place to add observability later.

Detection

  • ID: saas-api-design.api-consistency.consistent-response-format
  • Severity: medium
  • What to look for: Enumerate all route handlers and for each classify the JSON response structure returned. Check whether responses follow a consistent envelope pattern. Common patterns: { data: ..., error: null } vs { success: true, result: ... } vs raw data with no wrapper. Also check error responses — do they consistently return { error: "message" } or { message: "..." } or raw strings or HTTP status codes only? Inconsistency means API consumers have to handle multiple shapes.
  • Pass criteria: At least 80% of route handlers return responses in the same structural shape. Error responses use a consistent shape across routes.
  • Fail criteria: Handlers return different envelope shapes — some return { data: ... }, others return raw objects, others return { success: ..., result: ... }. Error handling returns different shapes across routes.
  • Skip (N/A) when: Fewer than 3 API route handlers exist. Signal: 2 or fewer route handler files detected.
  • Detail on fail: Describe the inconsistency found (e.g., "Auth routes return { user, token } directly; data routes return { data, meta }; error routes return { message } vs { error }"). Max 500 chars.
  • Remediation: Define a standard response envelope in src/lib/api-response.ts and apply it everywhere. A simple, widely-used pattern is: success responses return { data: <payload> } and error responses return { error: { code: "ERROR_CODE", message: "Human-readable message" } }. Create a helper function (e.g., apiSuccess(data) and apiError(code, message, status)) and replace all direct Response.json() calls with it. This makes client-side handling consistent and simplifies error logging.

External references

Taxons

History