Skip to main content
All error responses from the Happy Habitat API follow a unified JSON format, returned by the global exception middleware and controller methods.

Response Structure

Every error response contains the following fields in camelCase:
{
  "code": "string",
  "message": "string",
  "errors": null | { "field": ["error1", "error2"] },
  "traceId": "string | null"
}

Field Definitions

code
string
required
Error code for client-side logic (e.g., VALIDATION_ERROR, NOT_FOUND, UNAUTHORIZED, INTERNAL_ERROR)
message
string
required
Human-readable message suitable for display to end users. Messages are in Spanish.
errors
object | null
Optional field-level validation errors. Key is the field name, value is an array of error messages for that field.Only populated for VALIDATION_ERROR responses (HTTP 400).Example:
{
  "email": ["Email is required", "Email format is invalid"],
  "password": ["Password must be at least 8 characters"]
}
traceId
string | null
Request correlation identifier for debugging and support. Useful for tracing errors in logs.Generated from HttpContext.TraceIdentifier.

Example Error Responses

{
  "code": "VALIDATION_ERROR",
  "message": "Uno o más campos tienen errores de validación.",
  "errors": {
    "name": ["Name is required"],
    "email": ["Invalid email format"]
  },
  "traceId": "0HN7GFQJ5KQVM:00000001"
}

Error Response Generation

Global Exception Middleware

The API uses a global exception handling middleware (ExceptionHandlingMiddleware) that catches unhandled exceptions and converts them to standardized error responses. Source location: HappyHabitat.API/Middleware/ExceptionHandlingMiddleware.cs Exception mapping:
  • InvalidOperationException → 400 INVALID_OPERATION
  • ArgumentException → 400 BAD_REQUEST
  • KeyNotFoundException → 404 NOT_FOUND
  • UnauthorizedAccessException → 401 UNAUTHORIZED
  • DbUpdateException → 500 DATABASE_ERROR
  • All other exceptions → 500 INTERNAL_ERROR

Model Validation

Model validation errors (DataAnnotations) are automatically handled by ASP.NET Core and return a VALIDATION_ERROR response. Configuration: Program.cs line 28-43

Controller Extension Methods

Controllers can use extension methods to return standardized error responses:
// Return 400 Bad Request with custom code and message
return this.BadRequestApiError("BAD_REQUEST", "Usuario no está registrado como residente.");

// Return 404 Not Found with default message
return this.NotFoundApiError();

// Return 404 Not Found with custom message
return this.NotFoundApiError("Residente no encontrado.");

// Return 409 Conflict with custom code and message
return this.ConflictApiError("DUPLICATE_RESOURCE", "El recurso ya existe.");
Source location: HappyHabitat.API/Extensions/ControllerBaseExtensions.cs

Development vs Production

Error message detail varies by environment:
  • Development: Full exception messages and stack traces are included
  • Production: Generic user-friendly messages are shown for security
The traceId is always included for correlation with server logs.

Production Message Examples

Error TypeProduction Message
DATABASE_ERROR”Error al guardar los datos. Intente de nuevo.”
INTERNAL_ERROR”Ha ocurrido un error interno. Intente más tarde.”

Development Behavior

In development mode, the actual exception message is returned for debugging purposes.

Client Implementation Guidelines

Displaying Errors

  • Primary message: Display the message field to users as the main error notification
  • Field errors: If errors is present, show field-specific messages next to the corresponding form inputs
  • Error codes: Use the code field for programmatic error handling (e.g., redirect to login on UNAUTHORIZED)

Using Trace IDs

  • Include traceId in support requests for faster issue resolution
  • Log traceId in development environments for debugging
  • Store traceId temporarily for error reporting workflows

Example Client Handling

try {
  const response = await fetch('/api/resource', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify(data)
  });
  
  if (!response.ok) {
    const error = await response.json();
    
    // Handle by error code
    switch (error.code) {
      case 'UNAUTHORIZED':
        redirectToLogin();
        break;
      case 'VALIDATION_ERROR':
        // Display field-specific errors
        if (error.errors) {
          Object.entries(error.errors).forEach(([field, messages]) => {
            showFieldError(field, messages);
          });
        }
        break;
      case 'NOT_FOUND':
        show404Page();
        break;
      default:
        // Show general error message
        showNotification(error.message, 'error');
    }
    
    // Log trace ID for debugging
    console.error(`Error [${error.code}]: ${error.message}`, {
      traceId: error.traceId,
      errors: error.errors
    });
  }
} catch (err) {
  console.error('Request failed:', err);
  showNotification('Network error. Please try again.', 'error');
}

TypeScript Types

interface ApiErrorResponse {
  code: string;
  message: string;
  errors?: Record<string, string[]> | null;
  traceId?: string | null;
}

// Error code constants
type ErrorCode = 
  | 'VALIDATION_ERROR'
  | 'BAD_REQUEST'
  | 'INVALID_OPERATION'
  | 'UNAUTHORIZED'
  | 'NOT_FOUND'
  | 'DATABASE_ERROR'
  | 'INTERNAL_ERROR';

Build docs developers (and LLMs) love