Error Handling

Understanding API errors and how to handle them gracefully.

Error Response Format

All error responses follow a consistent JSON format:

{
  "error": "Human-readable error message",
  "code": "ERROR_CODE",
  "errors": ["Detailed error 1", "Detailed error 2"]
}
  • error - A human-readable description of the error
  • code - A machine-readable error code (optional)
  • errors - Array of detailed error messages (for validation errors)

HTTP Status Codes

Status Codes

ParameterTypeDescription
400
BAD_REQUEST
Invalid request body or parameters
401
UNAUTHORIZED
Missing or invalid API key
403
FORBIDDEN
Insufficient permissions (scope)
404
NOT_FOUND
Resource not found
409
CONFLICT
Resource conflict (e.g., duplicate, slot taken)
429
RATE_LIMITED
Too many requests
500
INTERNAL_ERROR
Server error

Common Error Examples

Authentication Error

401 Unauthorized
{
  "error": "API key is required",
  "code": "UNAUTHORIZED"
}

Permission Error

403 Forbidden
{
  "error": "Insufficient permissions. Required scope: events:write",
  "code": "FORBIDDEN"
}

Validation Error

400 Bad Request
{
  "error": "Validation failed",
  "errors": [
    "title is required",
    "start_date must be a valid ISO 8601 date",
    "slots must be a positive number"
  ]
}

Resource Not Found

404 Not Found
{
  "error": "Event not found",
  "code": "NOT_FOUND"
}

Booking Conflict

409 Conflict
{
  "error": "This time slot is already booked",
  "code": "SLOT_TAKEN"
}

Rate Limited

429 Too Many Requests
{
  "error": "Rate limit exceeded",
  "code": "RATE_LIMITED",
  "retry_after": 45
}

Handling Errors in Code

error-handling.ts
async function createEvent(eventData: EventInput) {
  try {
    const response = await fetch('/api/v1/events', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(eventData),
    });

    if (!response.ok) {
      const error = await response.json();

      switch (response.status) {
        case 400:
          // Validation error - show to user
          console.error('Validation failed:', error.errors);
          throw new ValidationError(error.errors);

        case 401:
          // Auth error - redirect to login
          throw new AuthError('Please log in again');

        case 403:
          // Permission error - show message
          throw new PermissionError(error.error);

        case 429:
          // Rate limited - retry later
          const retryAfter = error.retry_after || 60;
          throw new RateLimitError(`Try again in ${retryAfter}s`);

        default:
          throw new ApiError(error.error || 'An error occurred');
      }
    }

    return await response.json();
  } catch (error) {
    // Handle network errors
    if (error instanceof TypeError) {
      throw new NetworkError('Please check your connection');
    }
    throw error;
  }
}

Best Practices

Always Check Status Codes

Don't assume a response is successful. Always check the HTTP status code before processing the response body.

Handle Validation Errors Gracefully

When you receive a 400 error with an errors array, display these messages to your users so they can correct their input.

Implement Retry Logic

For 429 and 5xx errors, implement exponential backoff retry logic. Use the Retry-After header when available.

Log Errors for Debugging

Log the full error response including status code, error message, and any error codes for easier debugging.