ADR-0025: RFC 7807 for API Error Handling
Status
Accepted - 2025-01-26
Context
TVL Platform needs consistent, machine-readable error responses for API clients.
Decision
RFC 7807 Problem Details for all API error responses.
Rationale
- Standard Format: Industry-standard error format
- Machine-Readable: Structured JSON for client parsing
- Human-Readable: Clear error messages
- Debuggable: Trace IDs for distributed tracing
- Extensible: Custom fields for domain-specific errors
Error Response Format
{
  "type": "https://api.tvl.com/errors/validation-error",
  "title": "Validation Error",
  "status": 400,
  "detail": "Unit name is required and must be between 1-255 characters",
  "instance": "/api/v1/units/123",
  "traceId": "550e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2025-01-26T14:30:00Z"
}
Field Validation Errors
{
  "type": "https://api.tvl.com/errors/validation-error",
  "title": "Validation Error",
  "status": 400,
  "detail": "Request validation failed on 2 fields",
  "instance": "/api/v1/bookings",
  "traceId": "550e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2025-01-26T14:30:00Z",
  "errors": [
    {
      "field": "guestName",
      "code": "REQUIRED",
      "message": "Guest name is required"
    },
    {
      "field": "totalCents",
      "code": "MIN_VALUE",
      "message": "Total must be greater than 0",
      "rejectedValue": -100
    }
  ]
}
Implementation
// src/errors/ProblemDetails.ts
export class ProblemDetails {
  constructor(
    public type: string,
    public title: string,
    public status: number,
    public detail: string,
    public instance: string,
    public traceId: string,
    public timestamp: string,
    public errors?: Array<{ field: string; code: string; message: string }>
  ) {}
  toJSON() {
    return {
      type: this.type,
      title: this.title,
      status: this.status,
      detail: this.detail,
      instance: this.instance,
      traceId: this.traceId,
      timestamp: this.timestamp,
      ...(this.errors && { errors: this.errors })
    };
  }
}
// src/plugins/errorHandler.ts
app.setErrorHandler((error, request, reply) => {
  const problem = new ProblemDetails(
    'https://api.tvl.com/errors/internal-server-error',
    'Internal Server Error',
    500,
    error.message,
    request.url,
    request.id,
    new Date().toISOString()
  );
  reply.status(500).send(problem.toJSON());
});
Error Types
| Type | Status | Description | 
|---|---|---|
| validation-error | 400 | Request validation failed | 
| authentication-required | 401 | Missing/invalid auth token | 
| forbidden | 403 | Valid token, insufficient permissions | 
| not-found | 404 | Resource doesn't exist | 
| conflict | 409 | Resource conflict (double booking) | 
| rate-limit-exceeded | 429 | Too many requests | 
| internal-server-error | 500 | Server error |