Skip to main content

Overview

The Twigz API provides comprehensive error handling with structured error responses, proper HTTP status codes, and detailed error information to help developers build robust applications.

Error Response Format

All API errors follow a consistent structure:
{
  "error": "Human-readable error message",
  "code": "MACHINE_READABLE_ERROR_CODE",
  "details": {
    "field": "specific field errors",
    "context": "additional context"
  },
  "timestamp": 1640995200000,
  "requestId": "req_123456789"
}

HTTP Status Codes

2xx Success

200 - OK
201 - Created
204 - No Content

4xx Client Errors

400 - Bad Request
401 - Unauthorized
403 - Forbidden
404 - Not Found
409 - Conflict

5xx Server Errors

500 - Internal Server Error
502 - Bad Gateway
503 - Service Unavailable

Payment Errors

402 - Payment Required
422 - Payment Failed
424 - Payment Processing

Common Error Types

Authentication Errors

When: User is not authenticated
{
  "error": "Authentication required",
  "code": "UNAUTHENTICATED",
  "message": "Please sign in to access this resource"
}
Solution: Redirect to login page or show authentication prompt
When: User lacks required permissions
{
  "error": "Insufficient permissions",
  "code": "FORBIDDEN", 
  "details": {
    "requiredRole": "store_owner",
    "userRole": "customer",
    "resource": "store_management"
  }
}
Solution: Show access denied message or upgrade account prompt

Validation Errors

When: Request data is invalid
{
  "error": "Validation failed",
  "code": "VALIDATION_ERROR",
  "details": {
    "name": "Product name must be between 1 and 100 characters",
    "price": "Price must be a positive number",
    "category": "Category ID is required"
  }
}
Solution: Show field-specific error messages to user
When: Resource already exists or conflicts
{
  "error": "Store already exists for this user",
  "code": "RESOURCE_CONFLICT",
  "details": {
    "existingStoreId": "j123456789",
    "storeName": "Mario's Pizza"
  }
}
Solution: Redirect to existing resource or offer update option

Business Logic Errors

When: Not enough inventory for order
{
  "error": "Insufficient stock for one or more items",
  "code": "INSUFFICIENT_STOCK",
  "details": {
    "productId": "p123456789",
    "productName": "Pizza Margherita",
    "requested": 5,
    "available": 3
  }
}
Solution: Update cart quantities or remove unavailable items
When: Store is not accepting orders
{
  "error": "Store is not accepting orders",
  "code": "STORE_INACTIVE",
  "details": {
    "storeId": "j123456789",
    "storeName": "Mario's Pizza",
    "status": "temporarily_closed",
    "reason": "Store is temporarily closed for maintenance"
  }
}
Solution: Show store status and suggest alternatives

Payment Errors

When: Payment method declined
{
  "error": "Payment declined",
  "code": "PAYMENT_DECLINED",
  "details": {
    "declineCode": "insufficient_funds",
    "paymentMethod": "card",
    "lastFour": "4242"
  }
}
Solution: Ask user to try different payment method
When: Payment processing error
{
  "error": "Payment processing failed",
  "code": "PAYMENT_PROCESSING_ERROR",
  "details": {
    "paymentIntentId": "pi_123456789",
    "stripeError": "Your card was declined",
    "retryable": true
  }
}
Solution: Allow retry or suggest alternative payment method

Error Handling Patterns

React Error Boundary

import React from 'react';
import { ConvexError } from 'convex/values';

interface ErrorBoundaryState {
  hasError: boolean;
  error: Error | null;
}

class ApiErrorBoundary extends React.Component<
  React.PropsWithChildren<{}>,
  ErrorBoundaryState
> {
  constructor(props: React.PropsWithChildren<{}>) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    console.error('API Error caught by boundary:', error, errorInfo);
    
    // Report to error tracking service
    // reportError(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      const error = this.state.error;
      
      if (error instanceof ConvexError) {
        // Handle Convex-specific errors
        return (
          <div className="bg-red-50 p-4 rounded-lg">
            <h2 className="text-lg font-semibold text-red-900 mb-2">
              Something went wrong
            </h2>
            <p className="text-red-700">{error.data}</p>
            <button 
              onClick={() => this.setState({ hasError: false, error: null })}
              className="mt-2 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
            >
              Try Again
            </button>
          </div>
        );
      }
      
      // Handle other errors
      return (
        <div className="bg-gray-50 p-4 rounded-lg">
          <h2 className="text-lg font-semibold text-gray-900 mb-2">
            Unexpected Error
          </h2>
          <p className="text-gray-700">
            An unexpected error occurred. Please refresh the page or contact support.
          </p>
          <button 
            onClick={() => window.location.reload()}
            className="mt-2 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
          >
            Refresh Page
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

export default ApiErrorBoundary;

Retry Mechanisms

async function withRetry<T>(
  fn: () => Promise<T>,
  maxRetries: number = 3,
  delay: number = 1000
): Promise<T> {
  let lastError: any;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;
      
      // Don't retry on client errors (4xx)
      if (error instanceof ConvexError) {
        const errorData = error.data;
        if (typeof errorData === 'object' && errorData.status >= 400 && errorData.status < 500) {
          throw error;
        }
      }
      
      if (attempt === maxRetries) {
        throw lastError;
      }
      
      // Exponential backoff
      await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, attempt - 1)));
    }
  }
  
  throw lastError;
}

// Usage
const result = await withRetry(async () => {
  return await convex.mutation(api.shared.products.createProduct, productData);
}, 3, 1000);
The API uses Convex’s built-in error handling which provides structured errors with proper typing and automatic retry mechanisms for transient failures.
Always handle errors gracefully in your UI and provide clear feedback to users about what went wrong and how to resolve it.