Skip to main content

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

  1. Standard Format: Industry-standard error format
  2. Machine-Readable: Structured JSON for client parsing
  3. Human-Readable: Clear error messages
  4. Debuggable: Trace IDs for distributed tracing
  5. 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

TypeStatusDescription
validation-error400Request validation failed
authentication-required401Missing/invalid auth token
forbidden403Valid token, insufficient permissions
not-found404Resource doesn't exist
conflict409Resource conflict (double booking)
rate-limit-exceeded429Too many requests
internal-server-error500Server error

References