Skip to content

ADR 006: API Response Normalization

Status

Accepted

Context

The API currently has inconsistent response formats across different endpoints:

  • Some endpoints return data wrapped in { data: ... }
  • Others return data directly at the root level
  • Error responses vary between plain text and structured objects
  • No standardized metadata like timestamps or request IDs
  • Difficult for API consumers to handle responses predictably

Decision

We will implement a standardized response structure for all API responses with the following structure:

Success Response Format

{
  "success": true,
  "data": <actual_payload>
}

Error Response Format

{
  "success": false,
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "Human-readable error message",
    "details": { "optional": "context" }
  }
}

Standard Error Codes

We define a fixed set of error codes for common scenarios:

  • VALIDATION_ERROR - Request validation failed (400)
  • UNAUTHORIZED - Authentication required (401)
  • FORBIDDEN - Insufficient permissions (403)
  • RESOURCE_NOT_FOUND - Resource doesn't exist (404)
  • BAD_REQUEST - General client error (400)
  • INTERNAL_ERROR - Server error (500)

Implementation

We provide helper functions in app/helpers/api-response.ts:

// Success response
success(data, { status?, headers? })

// Error response
error(
  { code, message, details? },
  { status?, headers? }
)

// Throw error response
throwError(
  { code, message, details? },
  { status?, headers? }
)

Zod Schema Integration

For OpenAPI documentation, we provide:

createSuccessResponseSchema(dataSchema); // Wraps data schema
ErrorResponseSchema; // Standard error schema

These integrate with the existing OpenAPI registry to auto-generate correct API documentation.

Consequences

Positive

  • Predictable API structure - Clients always know where to find data or errors
  • Better error handling - Structured error codes for programmatic handling
  • Type safety - Zod schemas enforce structure at runtime and compile-time
  • OpenAPI compatibility - Automatic documentation generation
  • Minimal overhead - Simple structure without unnecessary metadata
  • Clear distinction - Success has data, errors have error